이 가이드에서는 Paging 라이브러리 개요를 기반으로 앱의 아키텍처 요구에 맞게 앱의 데이터 로드 솔루션을 맞춤설정하는 방법을 설명합니다.
식별 가능한 목록 구성
일반적으로 UI 코드는 앱의 ViewModel
에 있는 LiveData<PagedList>
객체(또는 RxJava2를 사용하고 있다면 Flowable<PagedList>
또는 Observable<PagedList>
객체)를 관찰합니다. 식별 가능한 이 객체는 앱 목록 데이터의 콘텐츠와 표시 간에 연결을 형성합니다.
이러한 식별 가능한 PagedList
객체 중 하나를 만들려면 DataSource.Factory
의 인스턴스를 LivePagedListBuilder
또는 RxPagedListBuilder
객체에 전달합니다. DataSource
객체가 단일 PagedList
를 위한 페이지를 로드합니다. Factory 클래스는 데이터베이스 테이블 무효화, 네트워크 새로고침과 같은 콘텐츠 업데이트에 응답하여 PagedList
의 새로운 인스턴스를 생성합니다. Room 지속성 라이브러리는 DataSource.Factory
객체를 자동으로 제공할 수 있습니다. 또는 개발자가 직접 고유한 객체를 빌드할 수도 있습니다.
다음 코드 스니펫은 Room의 DataSource.Factory
빌드 기능을 사용하여 앱의 ViewModel
클래스에 LiveData<PagedList>
의 새로운 인스턴스를 만드는 방법을 보여줍니다.
Kotlin
@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> }
자바
@Dao public interface ConcertDao { // The Integer type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") DataSource.Factory<Integer, Concert> concertsByDate(); }
Kotlin
// The Int type argument corresponds to a PositionalDataSource object. val myConcertDataSource : DataSource.Factory<Int, Concert> = concertDao.concertsByDate() val concertList = myConcertDataSource.toLiveData(pageSize = 50)
자바
// The Integer type argument corresponds to a PositionalDataSource object. DataSource.Factory<Integer, Concert> myConcertDataSource = concertDao.concertsByDate(); LiveData<PagedList<Concert>> concertList = LivePagedListBuilder(myConcertDataSource, /* page size */ 50).build();
고유한 페이징 구성 정의
고급 사례에 맞게 LiveData<PagedList>
를 더 자세히 구성하려면 직접 고유한 페이징 구성을 정의하면 됩니다. 특히 다음 속성을 정의할 수 있습니다.
- 페이지 크기: 각 페이지의 항목 수입니다.
- 미리 가져오기 범위: 앱 UI에서 마지막으로 표시되는 항목이 있다면 마지막 항목 이후에 Paging 라이브러리가 미리 가져오려고 시도해야 하는 항목 수입니다. 이 값은 페이지 크기보다 몇 배 더 커야 합니다.
- 자리표시자 있음: UI에서 로드가 아직 완료되지 않은 목록 항목의 자리표시자를 표시할지를 결정합니다. 자리표시자 사용의 이점과 단점에 관한 자세한 내용은 UI에서 자리표시자 제공 방법을 참고하세요.
Paging 라이브러리가 앱의 데이터베이스에서 목록을 로드할 때 더 세밀하게 제어하고자 한다면 다음 코드 스니펫에서와 같이 맞춤 Executor
객체를 LivePagedListBuilder
에 전달합니다.
Kotlin
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 )
자바
PagedList.Config myPagingConfig = new PagedList.Config.Builder() .setPageSize(50) .setPrefetchDistance(150) .setEnablePlaceholders(true) .build(); // The Integer type argument corresponds to a PositionalDataSource object. DataSource.Factory<Integer, Concert> myConcertDataSource = concertDao.concertsByDate(); LiveData<PagedList<Concert>> concertList = new LivePagedListBuilder<>(myConcertDataSource, myPagingConfig) .setFetchExecutor(myExecutor) .build();
정확한 데이터 소스 유형 선택
다음과 같이 소스 데이터의 구조를 가장 잘 처리하는 데이터 소스에 연결하는 것이 중요합니다.
- 로드하는 페이지에 다음/이전 키가 삽입된다면
PageKeyedDataSource
를 사용합니다. 예를 들어 네트워크에서 소셜 미디어 게시물을 가져오는 중이라면 특정 로드의nextPage
토큰을 후속 로드에 전달해야 할 수 있습니다. - N개 항목의 데이터를 사용하여 N+1개 항목을 가져와야 한다면
ItemKeyedDataSource
를 사용합니다. 예를 들어 토론 앱의 대화식 댓글을 가져온다면 다음 댓글 내용을 받기 위해 마지막 댓글의 ID를 전달해야 할 수도 있습니다. - 데이터 저장소에서 선택하는 위치에서 데이터 페이지를 가져와야 한다면
PositionalDataSource
를 사용합니다. 이 클래스는 선택하는 위치와 관계없이 그 위치에서 시작하는 일련의 데이터 항목 요청을 지원합니다. 예를 들어 요청은 위치 1500부터 시작하여 50개의 데이터 항목을 반환할 수 있습니다.
데이터가 잘못되었을 때 알림
Paging 라이브러리를 사용 중이라면 테이블 또는 행이 오래되었을 때 앱의 다른 레이어에 알릴지는 데이터 영역에 따라 달라집니다. 오래 되었음을 알리려면 앱에 선택한 DataSource
클래스에서 invalidate()
를 호출합니다.
고유한 데이터 소스 빌드
맞춤 로컬 데이터 솔루션을 사용하거나 네트워크에서 직접 데이터를 로드한다면 DataSource
서브클래스 중 하나를 구현할 수 있습니다. 다음 코드 스니펫은 Concert의 시작 시간에 가져오는 데이터 소스를 보여줍니다.
Kotlin
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) } }
자바
public class ConcertTimeDataSource extends ItemKeyedDataSource<Date, Concert> { @NonNull @Override public Date getKey(@NonNull Concert item) { return item.getStartTime(); } @Override public void loadInitial(@NonNull LoadInitialParams<Date> params, @NonNull LoadInitialCallback<Concert> callback) { List<Concert> items = fetchItems(params.key, params.requestedLoadSize); callback.onResult(items); } @Override public void loadAfter(@NonNull LoadParams<Date> params, @NonNull LoadCallback<Concert> callback) { List<Concert> items = fetchItemsAfter(params.key, params.requestedLoadSize); callback.onResult(items); }
그런 다음 구체적인 DataSource.Factory
서브클래스를 만들어 맞춤설정된 이 데이터를 PagedList
객체에 로드할 수 있습니다. 다음 코드 스니펫은 이전 코드 스니펫에 정의된 맞춤 데이터 소스의 새 인스턴스를 생성하는 방법을 보여줍니다.
Kotlin
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 } }
자바
public class ConcertTimeDataSourceFactory extends DataSource.Factory<Date, Concert> { private MutableLiveData<ConcertTimeDataSource> sourceLiveData = new MutableLiveData<>(); private ConcertDataSource latestSource; @Override public DataSource<Date, Concert> create() { latestSource = new ConcertTimeDataSource(); sourceLiveData.postValue(latestSource); return latestSource; } }
콘텐츠 업데이트 작동 방식 고려
식별 가능한 PagedList
객체를 구성할 때 콘텐츠 업데이트 작동 방식을 고려해야 합니다. Room 데이터베이스에서 직접 데이터를 로드하면 업데이트가 앱의 UI로 자동 푸시됩니다.
페이징된 네트워크 API를 사용할 때 일반적으로 '스와이프하여 새로고침'과 같은 사용자 상호작용은 최근에 사용된 DataSource
를 무효화하는 신호로 사용됩니다. 무효화되면 데이터 소스의 새 인스턴스를 요청합니다. 다음 코드 스니펫은 이러한 동작을 보여줍니다.
Kotlin
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() }
자바
public class ConcertActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { // ... viewModel.getRefreshState() .observe(this, new Observer<NetworkState>() { // Shows one possible way of triggering a refresh operation. @Override public void onChanged(@Nullable MyNetworkState networkState) { swipeRefreshLayout.isRefreshing = networkState == MyNetworkState.LOADING; } }; swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshListener() { @Override public void onRefresh() { viewModel.invalidateDataSource(); } }); } } public class ConcertTimeViewModel extends ViewModel { private LiveData<PagedList<Concert>> concertList; private DataSource<Date, Concert> mostRecentDataSource; public ConcertTimeViewModel(Date firstConcertStartTime) { ConcertTimeDataSourceFactory dataSourceFactory = new ConcertTimeDataSourceFactory(firstConcertStartTime); mostRecentDataSource = dataSourceFactory.create(); concertList = new LivePagedListBuilder<>(dataSourceFactory, 50) .setFetchExecutor(myExecutor) .build(); } public void invalidateDataSource() { mostRecentDataSource.invalidate(); } }
데이터 매핑 제공
Paging 라이브러리는 DataSource
에 의해 로드된 항목의 항목 기반 변환과 페이지 기반 변환을 지원합니다.
다음 코드 스니펫에서 Concert 이름과 Concert 날짜의 조합은 이름과 날짜가 모두 포함된 단일 문자열로 매핑됩니다.
Kotlin
class ConcertViewModel : ViewModel() { val concertDescriptions : LiveData<PagedList<String>> init { val concerts = database.allConcertsFactory() .map { "${it.name} - ${it.date}" } .toLiveData(pageSize = 50) } }
자바
public class ConcertViewModel extends ViewModel { private LiveData<PagedList<String>> concertDescriptions; public ConcertViewModel(MyDatabase database) { DataSource.Factory<Integer, Concert> factory = database.allConcertsFactory().map(concert -> concert.getName() + "-" + concert.getDate()); concertDescriptions = new LivePagedListBuilder<>( factory, /* page size */ 50).build(); } }
이 코드 스니펫은 항목이 로드된 후 항목을 래핑, 변환 또는 준비하려는 경우에 유용할 수 있습니다. 이 작업은 fetch executor에서 실행되기 때문에 디스크에서 읽거나 별도의 데이터베이스를 쿼리하는 것과 같이 리소스를 많이 사용하는 작업을 실행하게 될 수 있습니다.
의견 보내기
다음 리소스를 통해 의견을 보내고 아이디어를 공유해 주세요.
- Issue Tracker
- 버그를 수정할 수 있도록 문제를 신고해 주세요.
추가 리소스
Paging 라이브러리에 관해 자세히 알아보려면 다음 리소스를 참고하세요.
샘플
Codelab
동영상
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- Paging 3으로 이전
- Paging 2 라이브러리 개요
- 페이징된 목록 표시