봄날은 갔다. 이제 그 정신으로 공부하자

Paging Library - Gather Paged Data 본문

Android jetpack

Paging Library - Gather Paged Data

길재의 그 정신으로 공부하자 2020. 11. 24. 14:31

이 글에서는 “Paging Library - Overview(als2019.tistory.com/8)”를 기반으로 앱의 아키텍처 요구에 맞게 앱의 데이터 로드 솔루션을 맞춤설정하는 방법을 설명합니다.

 

observable list 생성

일반적으로 UI 코드는 앱의 ViewModel에 있는 LiveData<PagedList> 개체(또는 RxJava2를 사용하고 있다면 Flowable<PagedList> 또는 Observable<PagedList> 개체)를 관찰합니다. 식별 가능한 개체는 앱 목록 데이터의 콘텐츠와 표시 간에 연결을 형성합니다.

이러한 식별 가능한 PagedList 개체 중 하나를 생성하려면 DataSource.Factory 인스턴스를 LivePagedListBuilder 또는 RxPagedListBuilder 개체에 전달해야 합니다. DataSource 개체는 단일 PagedList를 위한 페이지를 로드합니다.

Factory 클래스는 데이터베이스 테이블 무효화 및 네트워크 새로고침과 같은 콘텐츠 업데이트에 대응하여 새로운 PagedList 인스턴스를 생성합니다. Room Persistence Library는 DataSource.Factory 개체를 제공할 수 있다. 또는 개발자가 직접 Object를 빌드할 수도 있습니다.

아래 예제는 Room의 DataSource.Factory 빌드 기능을 사용하여 앱의 ViewModel 클래스에 새로운 LiveData<PagedList> 인스턴스를 생성하는 방법을 보여줍니다.

// ConcertDao
@Dao
interface ConcertDao {
    // The Int type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    fun concertsByDate(): DataSource.Factory<Int, Concert>
}

// ConcertViewModel
// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =
       concertDao.concertsByDate()

val concertList = myConcertDataSource.toLiveData(pageSize = 50)

 

사용자 paging configuration 정의

LiveData<pagedList>를 더 자세히 구성하기 위해 사용자 페이징 구성을 정의할 수도 있으며 특히 다음 속성을 정의할 수 있습니다.

 

Paging Library가 앱의 데이터베이스에서 목록을 로드할 때 더 세밀하게 제어하려면 아래 예제와 같이 맞춤 Excutor 개체를 LivaPagedListBuilder에 전달해야 합니다.

// ConcertViewModel
val myPagingConfig = Config(
        pageSize = 50,
        prefetchDistance = 150,
        enablePlaceholders = true
)

// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =
        concertDao.concertsByDate()

val concertList = myConcertDataSource.toLiveData(
        pagingConfig = myPagingConfig,
        fetchExecutor = myExecutor
)

 

정확한 Data source type 선택

아래 설명과 같이 Data source의 구조를 가장 잘 처리하는 Data source에 연결하는 것이 중요합니다.

 

데이터가 잘 못 되었을 때 알림

Paging Library를 사용 중이라면 테이블 또는 행이 오래되었을 때 앱의 다른 레이어에 알릴지는 데이터 영역에 따라 달라지며 알림을 받을 수 있도록 설정하려면 앱과 관련하여 선택한 DataSource 클래스에서 invalidata()함수를 호출합니다.

 

사용자 Data Source 빌드

Custom local data 솔루션을 사용하거나 네트워크에서 직접 데이터를 로드한다면 DataSource 서브클래스 중 하나를 구현할 수 있습니다. 아래 예제는 Concert의 시작 시간에 가져오는 데이터 소스를 보여줍니다.

class ConcertTimeDataSource() :
        ItemKeyedDataSource<Date, Concert>() {
    override fun getKey(item: Concert) = item.startTime

    override fun loadInitial(
            params: LoadInitialParams<Date>,
            callback: LoadInitialCallback<Concert>) {
        val items = fetchItems(params.requestedInitialKey, params.requestedLoadSize)
        callback.onResult(items)
    }

    override fun loadAfter(
            params: LoadParams<Date>,
            callback: LoadCallback<Concert>) {
        val items = fetchItemsAfter(
            date = params.key,
            limit = params.requestedLoadSize)
        callback.onResult(items)
    }
}

구체적인 DataSource.Factory 서브클래스를 생성함으로써 맞춤설정된 데이터를 PagedList 개체에 로드할 수 있습니다. 

아래 예제는 위 예제에서 정의된 Custom Data source의 새 인스턴스를 생성하는 방법을 보여줍니다.

class ConcertTimeDataSourceFactory :
        DataSource.Factory<Date, Concert>() {
    val sourceLiveData = MutableLiveData<ConcertTimeDataSource>()
    var latestSource: ConcertDataSource?
    override fun create(): DataSource<Date, Concert> {
        latestSource = ConcertTimeDataSource()
        sourceLiveData.postValue(latestSource)
        return latestSource
   }
}

 

콘텐츠 업데이트 작동 방식 고려 사항

식별 가능한 PagedList 개체를 구성할 때 콘텐츠 업데이트 작동 방식을 고려해야 합니다.

Room Database에서 직접 데이터를 로드하면 업데이트가 앱의 UI로 자동 푸시됩니다.

페이징된 네트워크 API를 사용할 때 일반적으로 '스와이프하여 새로고침'과 같은 사용자 상호작용은 가장 최근에 사용된 DataSource를 무효화하는 신호로 사용되며, 무효화되면 데이터 소스의 새 인스턴스를 요청합니다.

아래 예제는 이러한 동작들을 보여줍니다.

class ConcertActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        concertTimeViewModel.refreshState.observe(this, Observer {
             // Shows one possible way of triggering a refresh operation.
             swipeRefreshLayout.isRefreshing =
                 it == MyNetworkState.LOADING
          })
          swipeRefreshLayout.setOnRefreshListener {
              concertTimeViewModel.invalidateDataSource()
          }
      }
  }

class ConcertTimeViewModel(firstConcertStartTime: Date) : ViewModel() {
    val dataSourceFactory = ConcertTimeDataSourceFactory(firstConcertStartTime)
    val concertList: LiveData<PagedList<Concert>> =
            dataSourceFactory.toLiveData(
                pageSize = 50,
                fetchExecutor = myExecutor
            )

    fun invalidateDataSource() =
            dataSourceFactory.sourceLiveData.value?.invalidate()
}

 

데이터 매핑 제공

Paging Library는 DataSource에 의해 로드된 항목의 항목 기반 및 페이지 기반 변환을 지원합니다.

아래 예제에서 Concert 이름과 Concert 날짜의 조합은 이름과 날짜가 모두 포함된 단일 문자열로 매핑됩니다.

class ConcertViewModel : ViewModel() {
    val concertDescriptions : LiveData<PagedList<String>>
        init {
            val concerts = database.allConcertsFactory()
                    .map "${it.name} - ${it.date}" }
                    .toLiveData(pageSize = 50)
        }
    }
}

위 예제는 항목이 로드된 후 항목을 래핑, 변환 또는 준비하려는 경우에 유용할 수 있습니다. 

이 작업은 fetch executor에서 실행되기 때문에 디스크에서 읽거나 별도의 데이터베이스를 쿼리하는 것과 같이 리소스를 많이 사용하는 작업을 실행하게 될 수 있습니다.

'Android jetpack' 카테고리의 다른 글

MVVM + Koin 최소 샘플 앱 개발 - part 1  (0) 2020.12.04
Paging Library - Display paged lists  (0) 2020.11.24
Paging Library - Overview  (0) 2020.11.24
WorkManager 알아보기  (0) 2020.11.23
Android 백그라운드 처리 가이드  (0) 2020.11.23
Comments