[Android] Paging-compose Paging 구현하기 + 소스코드 들여다보기

Lee Yongin·2024년 4월 10일
0

안드로이드

목록 보기
12/12

Android xml+RecyclerView로 고통받던 건 옛날이야기로 느껴질 정도로 편리해진 LazyColumn+Paging 방법이 있다.

gradle 추가

1.0.0은 이 포스팅에서 이용하고자하는 collectAsLazyPagingItems이 없으므로 아래의 버전으로 추가해야 한다.

implementation("androidx.paging:paging-compose:3.2.0")

compose Paging을 하기위해 사용하는 것들

이름이 비슷해서 헷갈릴 수 있다. 자세히 정리하기 전에 간단하게 역할만 적어보자. 다 이해하고 보니 파이프라인 그림처럼 이해하면 참 쉬운 개념이다.

  1. PagingSource는 데이터를 공급하는 부분이다.
  2. Pager는 데이터가 흐르는 파이프라인이다.
  3. PagingData는 데이터를 페이징하기 쉽게 캡슐화한 클래스이다.
  4. PagingConfig는 데이터 공급을 디테일하게 설정하는 부분이다.

안드로이드 페이징 라이브러리 공식문서를 봐도 비슷한 내용으로 다이어그램을 그려주고 있으니 참고하면 좋다.

PagingSource

public abstract class PagingSource<Key : Any, Value : Any>

키값과 데이터를 가진 추상 클래스이다. Key값은 데이터를 로드하는 데 사용되는 식별자, Value는 데이터 자체의 유형이다.
PaginSource는 페이지 번호를 기준으로 항목 페이지를 로드한다.
해당 추상 클래스를 구현하게 되면 load, getRefreshKey라는 메서드를 구현해야 한다.

load()의 키 사용 및 업데이트

Paging에서 key는 페이지 번호라고 생각해도 무방하다.nextKey와 prevKey를 기준으로 페이지를 보여주고 필요에 따라서 데이터를 더 요청한다. nextKey가 null이면 load를 멈추고, prevKey가 null이면 앞 방향으로의 페이징을 안해준다.

override load

PagingSource의 생성자에서 제공된 매개변수를 load() 메서드에 전달함으로써 데이터를 로드한다.

override getRefreshKey

데이터가 새로고침될 때마다 자동으로 Paging라이브러리에서 호출되는 메서드이므로 꼭 구현해야 한다.
PagingState객체를 매개변수로 사용해서 데이터가 첫 로드 후 새로고침되거나 무효화되었을 때 키를 반환해 load()메서드로 전달하는 역할을 한다.

메소드 구현 예시

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PerfumeCommentResponseDto> {
        val pageNumber = params.key ?: 0
        try {
            val response =
                perfumeCommentRepository.getPerfumeCommentsLatest(
                    pageNumber,
                    cursor = cursor,
                    perfumeId = perfumeId
                )
            commentCounts = response.commentCount
            cursor = response.comments.get(response.comments.lastIndex).id
            val prevKey = if (pageNumber > 0) pageNumber - 1 else null
            val nextKey = if (response.lastPage) null else pageNumber + 1

            return LoadResult.Page(
                data = response.comments,
                prevKey = prevKey,
                nextKey = nextKey
            )
        } catch (e: Exception) {
            return LoadResult.Error(e)
        }
    }
    
override fun getRefreshKey(state: PagingState<Int, PerfumeCommentResponseDto>): Int? {
        return state.anchorPosition?.let {
            state.closestPageToPosition(it)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(it)?.nextKey?.minus(1)
        }
    }

PagingData

public class PagingData<T : Any> 

리스트 형식의 데이터를 prevKey, nextKey라는 키값 멤버변수들과 캡슐화한 데이터 클래스이다. 최종적으로 이 데이터 타입으로 LazyColumn까지 전달된다.

Pager

페이징 데이터를 스트림으로 만들어주는 파이프라인이다.

public class Pager<Key : Any, Value : Any>
// Experimental usage is propagated to public API via constructor argument.
@ExperimentalPagingApi constructor(
    config: PagingConfig,
    initialKey: Key? = null,
    remoteMediator: RemoteMediator<Key, Value>?,
    pagingSourceFactory: () -> PagingSource<Key, Value>
)
--생략--
}

Pager.kt의 생성자 부분이다. 잘 보면 pagingSourceFactory에 위에서 설명한 PagingSource 구현체 클래스가 들어간다. Pager의 가장 큰 역할은 pagingSourceFactory에서 나오는 데이터를 Flow로 바꿀 수 있는 메소드 flow가 수행한다.

flow 메소드! 데이터 스트림을 생성해주죠

아래의 코드는 Pager.kt의 flow 메소드이다.위에서 말했듯이 들어온 값을 플로우 데이터로 방출한다.

@OptIn(androidx.paging.ExperimentalPagingApi::class)
    public val flow: Flow<PagingData<Value>> = PageFetcher(
        pagingSourceFactory = if (
            pagingSourceFactory is SuspendingPagingSourceFactory<Key, Value>
        ) {
            pagingSourceFactory::create
        } else {
            // cannot pass it as is since it is not a suspend function. Hence, we wrap it in {}
            // which means we are calling the original factory inside a suspend function
            {
                pagingSourceFactory()
            }
        },
        initialKey = initialKey,
        config = config,
        remoteMediator = remoteMediator
    ).flow

PagingConfig

PagingSource로부터 얼마 만큼의 PagingData를 가져올지 정하는 클래스이다. 아래와 같이 Pager의 config 프로퍼티로 들어간다.

Pager(
	config = PagingConfig(pageSize = PAGE_SIZE),
    pagingSourceFactory = { latestPerfumeCommentPagingSource(perfumeId) }
    ).flow.cachedIn(viewModelScope)

참고자료

https://developer.android.com/topic/libraries/architecture/paging/v3-network-db?hl=ko#basic-usage
https://medium.com/@wooongyee/android-paging-3-library-840e63d46e7d

profile
f1을 좋아하는...🏆 f1처럼 빠르고 정확한 걸 좋아하는 안드로이드 개발자

0개의 댓글