Developing Myself Everyday
article thumbnail
Published 2023. 8. 16. 19:46
[AAC] Paging 라이브러리 Android/AAC

 

이 게시글은 아래의 공식 문서를 보고 작성했습니다.

 

페이징 라이브러리 개요  |  Android 개발자  |  Android Developers

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Paging 라이브러리 개요   Android Jetpack의 구성요소 Paging 라이브러리를 사용하면 로컬 저장소에서나 네트워크

developer.android.com

 

Android Paging 고급 Codelab  |  Android Developers

이 Codelab에서는 Paging 라이브러리를 포함하도록 샘플 앱을 수정하여 앱의 메모리 공간을 줄입니다.

developer.android.com


 

 

Paging 라이브러리를 사용하는 이유


Android Paging 라이브러리는 안드로이드 앱에서 대량의 데이터를 효율적으로 로드하고 표시하는 데 사용되는 라이브러리입니다. 특히 리스트 형태의 UI에서 스크롤링을 지원하며, 데이터를 페이지 단위로 로드하여 메모리 사용량을 관리하고 네트워크 및 디스크 I/O를 최적화하는 데 도움을 줍니다.

 

 

 

 

페이징 라이브러리를 사용하여 얻을 수 있는 이점

페이징 라이브러리에는 다음과 같은 기능이 있습니다.

  • Paging된 데이터의 메모리 내 캐싱. 이는 앱이 Paging 데이터로 작업하는 동안 시스템 리소스를 효율적으로 사용할 수 있도록 도와줍니다.
  • 내장된 요청 중복 제거 기능으로, 앱이 네트워크 대역폭과 시스템 리소스를 효율적으로 활용하도록 지원합니다.
  • 사용자가 로드된 데이터의 끝까지 스크롤할 때 구성 가능한 RecyclerView 어댑터가 자동으로 데이터를 요청합니다.
  • Kotlin 코루틴 및 Flow뿐만 아니라 LiveData 및 RxJava를 최고 수준으로 지원합니다.
  • 새로고침 및 재시도 기능을 포함하여 오류 처리를 기본으로 지원합니다.

 

 

라이브러리 아키텍처

Paging 라이브러리의 구성요소는 앱의 세 가지 레이어에서 작동합니다.

  • Repository 레이어
  • ViewModel 레이어
  • UI 레이어

 

Paging 라이브러리가 앱 아키텍처에 어떻게 적용되는지 보여주는 예

 

Repository 레이어

저장소 레이어의 기본 페이징 라이브러리 구성요소는 PagingSource입니다. 각 PagingSource 객체는 데이터 소스와 이 소스에서 데이터를 검색하는 방법을 정의합니다. PagingSource 객체는 네트워크 소스 및 로컬 데이터베이스를 포함한 단일 소스에서 데이터를 로드할 수 있습니다.

사용할 수 있는 다른 페이징 라이브러리 구성요소는 RemoteMediator입니다. RemoteMediator 객체는 로컬 데이터베이스 캐시가 있는 네트워크 데이터 소스와 같은 계층화된 데이터 소스의 페이징을 처리합니다.

ViewModel 레이어

Pager 구성요소는 PagingSource 객체 및 PagingConfig 구성 객체를 바탕으로 반응형 스트림에 노출되는 PagingData 인스턴스를 구성하기 위한 공개 API를 제공합니다.

ViewModel 레이어를 UI에 연결하는 구성요소는 PagingData입니다. PagingData 객체는 페이지로 나눈 데이터의 스냅샷을 보유하는 컨테이너입니다. PagingSource 객체를 쿼리하여 결과를 저장합니다.

UI 레이어

UI 레이어의 기본 페이징 라이브러리 구성요소는 페이지로 나눈 데이터를 처리하는 RecyclerView 어댑터인 PagingDataAdapter입니다.

또는 포함된 AsyncPagingDataDiffer 구성요소를 사용하여 고유한 맞춤 어댑터를 빌드할 수 있습니다.

 

 

 

 

페이징된 데이터 로드 및 표시


페이징된 데이터를 표시하려면 아래와 같은 몇 가지 단계를 거쳐야 합니다.

 

 1. 데이터 소스 정의

 2. PagingData 스트림 설정

 3. RecyclerView 어댑터 정의

 4. UI에 페이징된 데이터 표시

 

이제 이를 차근차근 알아보겠습니다.

 

 

데이터 소스 정의

먼저 데이터 소스를 정의해야 합니다. PagingSource에서 데이터 소스를 정의하고 이 소스에서 데이터를 가져오는 방법을 정의합니다. PagingData 객체는 사용자가 RecyclerView에서 스크롤할 때 생성되는 힌트가 로드되면 PagingSource에서 데이터를 쿼리합니다.

 

 

1. PagingSource를 구현하려면 (1) 페이징 키의 유형, (2) 데이터의 유형, (3) 데이터를 가져오는 위치를 정의해야 합니다.

 

 (1) 페이징 키의 유형 - 1을 기반으로 하는 색인 번호를 사용하므로 유형이 Int입니다.

 (2) 데이터의 유형 - `Repo 데이터 클래스` 데이터 소스를 로드합니다.

 (3) 데이터를 가져오는 위치 - `GithubService`에서 데이터를 가져옵니다.

 

class GithubPagingSource(
        private val service: GithubService,
        private val query: String
) : PagingSource<Int, Repo>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
        TODO("Not yet implemented")
    }
   override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {
        TODO("Not yet implemented")
    }

}

PagingSource에서는 두 가지 메서드(load()  getRefreshKey())를 구현해야 합니다.

 

 

 

2. load() 메서드 구현

 

load() 메서드는 사용자가 스크롤할 때 표시할 더 많은 데이터를 비동기식으로 가져오기 위해 Paging 라이브러리에서 load() 메서드를 호출합니다.

 

LoadParams 객체에는 실행할 로드 작업에 관한 정보가 포함됩니다. load() 메서드가 처음 호출되는 경우에는 LoadParams.key가 null입니다. 그렇기에 초기 페이지 값을 정의해야 합니다.  그렇기에 아래와 정의합니다.

private const val GITHUB_STARTING_PAGE_INDEX = 1

 

그렇게 해서 구현한  load() 메서드는 아래와 같습니다.

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
        val position = params.key ?: GITHUB_STARTING_PAGE_INDEX
        val apiQuery = query + IN_QUALIFIER
        return try {
            val response = service.searchRepos(apiQuery, position, params.loadSize)
            val repos = response.items
            val nextKey = if (repos.isEmpty()) {
                null
            } else {
                // initial load size = 3 * NETWORK_PAGE_SIZE
                // ensure we're not requesting duplicating items, at the 2nd request
                position + (params.loadSize / NETWORK_PAGE_SIZE)
            }
            LoadResult.Page(
                    data = repos,
                    prevKey = if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1,
                    nextKey = nextKey
            )
        } catch (exception: IOException) {
            return LoadResult.Error(exception)
        } catch (exception: HttpException) {
            return LoadResult.Error(exception)
        }
    }

 

load() 메서드의 반환값인 LoadResult 객체에는 로드 작업의 결과가 포함됩니다. LoadResult는 load() 호출이 성공했는지 여부에 따라 두 가지 형식 중 하나를 취하는 Sealed 클래스입니다.

  • 로드에 성공하면 LoadResult.Page 객체를 반환합니다.
  • 로드에 실패하면 LoadResult.Error 객체를 반환합니다.

 

 

3. getRefreshKey() 메서드 구현

getRefreshKey() 메서드는 데이터가 첫 로드 후 새로고침되거나 무효화 되었을 때 키를 반환하여 load() 메서드로 전달합니다. Paging 라이브러리는 다음에 데이터를 새로고침할 때 자동으로 이 메서드를 호출합니다.

override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {
        return state.anchorPosition?.let { anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
        }
    }

 

 

 

Repository에서 PagingData 빌드 및 구성

Pager 객체는 PagingSource 객체에서 load() 메서드를 호출하여 LoadParams 객체를 제공하고 반환되는 LoadResult 객체를 수신합니다.

class GithubRepository(private val service: GithubService) {

    fun getSearchResultStream(query: String): Flow<PagingData<Repo>> {
        return Pager(
                config = PagingConfig(
                pageSize = NETWORK_PAGE_SIZE,
                enablePlaceholders = false
             ),
                pagingSourceFactory = { GithubPagingSource(service, query) }
        ).flow
    }

    companion object {
        const val NETWORK_PAGE_SIZE = 50
    }
}

PagingConfig: 페이징 처리를 위한 구성을 정의합니다. pageSize는 한 페이지에 보여질 아이템 수를 나타냅니다. enablePlaceholders는 장소 홀더(placeholder) 사용 여부를 결정합니다.

pagingSourceFactory: 페이징 데이터 소스를 생성하는 함수를 전달합니다. 

 

 

 

RecyclerView 어댑터 정의

PagingData RecyclerView에 바인딩하려면 PagingDataAdapter를 사용해야 합니다. 그럼 PagingData 콘텐츠가 로드될 때마다 PagingDataAdapter에서 알림을 받은 다음 RecyclerView에 업데이트하라는 신호를 보냅니다.

class ReposAdapter : PagingDataAdapter<Repo, RepoViewHolder>(DiffUtil.ItemCallback) {
// body is unchanged
}

RecyclerView를 일반적으로 정의할 때와 동일하게 작동하기에 자세한 설명은 생략하겠습니다.

 

지금까지 PagingSource를 정의하고 앱에서 PagingData 스트림을 생성하는 방법을 만들었으며 PagingDataAdapter를 정의했습니다. 이제 이러한 요소를 함께 연결하여 페이징된 데이터를 활동에 표시할 준비할 차례입니다.

 

 

 

UI에 페이징된 데이터 표시

onCreate 또는 프래그먼트의 onViewCreated 메서드에서 다음 단계를 진행하면 됩니다.

  1. PagingDataAdapter 클래스의 인스턴스를 만듭니다.
  2. 페이징된 데이터를 표시할 RecyclerView 목록에 PagingDataAdapter 인스턴스를 전달합니다.
  3. PagingData 스트림을 확인하고 생성된 각 값을 어댑터의 submitData() 메서드에 전달합니다.
val viewModel by viewModels<ExampleViewModel>()

val reposAdapter = ReposAdapter(ReposComparator)
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = reposAdapter

// Activities can use lifecycleScope directly; fragments use
// viewLifecycleOwner.lifecycleScope.
lifecycleScope.launch {
  viewModel.flow.collectLatest { pagingData ->
    reposAdapter.submitData(pagingData)
  }
}

'Android > AAC' 카테고리의 다른 글

[AAC] WorkManager  (0) 2023.08.25
[AAC] DataStore with SharedPreferences  (0) 2023.08.24
[AAC] LiveData  (0) 2023.08.12
[AAC] LifeCycle  (0) 2023.08.03
profile

Developing Myself Everyday

@배준형

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!