Grusie 안드로이드 개발 기술 블로그

[Android] Paging 3.0 라이브러리 본문

안드로이드 개발/라이브러리

[Android] Paging 3.0 라이브러리

grusie 2024. 2. 29. 13:19
728x90
반응형
SMALL
페이징3 라이브러리란

Paging3는 JetPack 라이브러리 중 하나로, 데이터 소스로부터 데이터를 나누어 효과적으로 로딩 할 수 있게 해준다.
로컬 DB 혹은 네트워크에서 쉽게 데이터를 불러올 수 있도록 도와주기 때문에 개발 시간을 단축시켜 준다.
Paging3는 코틀린으로 우선 개발이 되고, 코루틴 및 Flow와 같은 새로운 방식의 비동기 작업으로 동작한다. 또한 RxJava와 LiveData도 지원한다.

 


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

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

 


Paging3 아키텍처

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

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

Paging3 아키텍처
이미지 출처 :https://developer.android.com/topic/libraries/architecture/paging/v3-overview?hl=ko

 

Repository 레이어

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

 

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

 

ViewModel 레이어

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

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

UI 레이어

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

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

 

더보기

참고 : 앱에서 UI에 Compose를 사용하는 경우 Paging을 UI 레이어에 통합할 때 androidx.paging:paging-compose 아티팩트를 사용할 것.

 


주요 클래스 소개

Repository Layer

더보기

PagingData

- 페이징된 데이터의 Container 역할을 한다. 데이터가 새로고침 될 때마다 이에 상응하는 PagingData가 별도로 생성된다.

 

PagingSource

- 로컬 데이터베이스 또는 네트워크로 데이터를 불러오는 것을 담당하는 추상 클래스이다.

 

RemoteMediator

- 네트워크(Remote)에서 불러온 데이터를 로컬 데이터베이스에 캐시(Cache)하여 불러오는 것을 담당한다.

 

주요 클래스들과 Paging라이브러리가 동작하는 과정 요약
이미지 출처 :  https://developer.android.com/topic/libraries/architecture/paging/v3-paged-data?hl=ko

페이징 소스를 활용해 데이터를 로드하는 과정

 

ViewModel Layer

더보기

Pager

- Repository Layer에서 구현된 PagingSource와 함께 PagingData 인스턴스를 구성하는 반응형 스트림을 생성한다.

- PagingSource에서 데이터를 로드하는 방법, 옵션을 정의한 PagingConfig 클래스와 함께 사용된다.

 

UI Layer

더보기

PagingDataAdapter

- PagingData를 RecyclerView에 바인딩하기 위해 사용된다.

- 데이터를 어느 시점에서 더 받아올 것인가 등 UI와 관련된 대부분의 일을 책임진다.

 


실제 구현 코드

의존성 추가

dependencies {
    def paging_version = "3.2.1"

    implementation "androidx.paging:paging-runtime:$paging_version"

    // alternatively - without Android dependencies for tests
    testImplementation "androidx.paging:paging-common:$paging_version"

    // optional - RxJava2 support
    implementation "androidx.paging:paging-rxjava2:$paging_version"

    // optional - RxJava3 support
    implementation "androidx.paging:paging-rxjava3:$paging_version"

    // optional - Guava ListenableFuture support
    implementation "androidx.paging:paging-guava:$paging_version"

    // optional - Jetpack Compose integration
    implementation "androidx.paging:paging-compose:3.3.0-alpha03"
}

 

PagingSource

class TestPagingSource: PagingSource<Int, TestPagingData>() {

    override fun getRefreshKey(state: PagingState<Int, TestPagingData>): Int? {
        TODO("Not yet implemented")
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TestPagingData> {
        TODO("Not yet implemented")
    }
}

- load() : 실제 데이터를 가져오는 로직을 구현하는 함수

- getRefreshKey() : 초기 key값이나, 데이터 로드 중단 후 로드 시 이전 position에서 중단된 key값을 가져오는 등 load에서 사용할 key값을 가져오는 로직구현을 위한 함수이다.

 

RemoteMediator

@OptIn(ExperimentalPagingApi::class)
class TestRemoteMediator: RemoteMediator<Int, TestPagingData>(){

    override suspend fun initialize(): InitializeAction {
        return InitializeAction.SKIP_INITIAL_REFRESH
    }

    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, TestPagingData>
    ): MediatorResult {
        MediatorResult.Success(endOfPaginationReached = true)
    }
}

- initallize() : 캐시된 데이터를 갱신시킬 것인지에 대한 로직 구현을 위한 함수

- load() : LoadType을 활용하여 데이터 추가로드 여부에 대한 로직을 구현한 뒤, 결과를 load함수의 반환형인 MediatorResult에 endOfPaginationReached 파라미터를 넘겨주어 데이터 로드를 끝마칠지 판단한다.

더보기

RemoteMediator 사용 시 특이점은 Key를 내부에서 제공해주거나 사용하지 않는다는 것이다.

(상황에 따라 key가 필요하다면 내부 DB를 사용하여 직접 생성 및 관리 하도록 한다.)

 

Pager

// PagerSource 사용
val pagingData: Flow<PagingData<TestPagingData>> = Pager(
    config = PagingConfig(pageSize = 10)
) {
    TestPagingSource()
    //pagingSourceFactory
}.flow.cachedIn(viewModelScope)

// RemoteMediator 사용
val pagingDataFlow: Flow<PagingData<TestPagingData>> = Pager(
    config = PagingConfig(pageSize = 10),
    remoteMediator = TestRemoteMediator()
) {
    TestPagingSource()
    //pagingSourceFactory
}.flow

- PagingSource나, RemoteMediator에서 Pageconfig의 정보를 토대로 PagingData를 생성해서, 스트림화 해주는 클래스이다.

- 스트림화 시에, Flow, LiveData, RxJava 등의 유형들도 지원한다.

- Pager를 통해 스트림을 만들어, ViewModel을 통해서 사용하면 된다.

 

ViewModel

class TestViewModel(private val repository: TestRepository): ViewModel() {

    //..

    fun getContent(): Flow<PagingData<TestPagingData>> {
        return repository.pagingData()
            .cachedIn(viewModelScope)
    }
}

- cachedIn() : CoroutineScope에서 데이터를 캐시할 수 있는 함수

 

View

class TestFragment : Fragment() {

    //..

    private fun initTest() {
        //..
        lifecycleScope.launch {
            viewModel.getContent().collectLatest {
                adapter.submitData(it)
            }
        }
    }

- 뷰모델에서 생성한 데이터를 뷰에서, CollectLatest로 수집해 UI에 표현 해 주면 된다.

 

참고

https://developer.android.com/topic/libraries/architecture/paging/v3-overview?hl=ko

 

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

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 페이징 라이브러리 개요 Android Jetpack의 구성요소 Paging

developer.android.com

 

 

후기

- 페이징을 기존에는 리사이클러뷰의 스크롤 리스너를 달아서, LastVisibleItem을 확인해, 페이징을 진행하였으나, Paging3라이브러리를 활용하면, 에러처리, 새로고침 등 다양한 기능들을 활용하여 안정적으로 사용할 수 있다는 것을 알게되었다.

물론 코드가 복잡하고, 만약 RemoteMediator를 활용한다면, 키를 따로 저장하는 Local DB를 만들어야 할 수도 있으니 상황에 따라서, 사용하면 될 것 같다.

반응형
LIST