이 가이드에서는 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>
의 새로운 인스턴스를 만드는 방법을 보여줍니다.
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>
}
@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();
}
ConcertViewModel
// 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
에 전달합니다.
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
)
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의 시작 시간에 가져오는 데이터 소스를 보여줍니다.
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
객체에 로드할 수 있습니다. 다음 코드 스니펫은 이전 코드 스니펫에 정의된 맞춤 데이터 소스의 새 인스턴스를 생성하는 방법을 보여줍니다.
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
를 무효화하는 신호로 사용됩니다. 무효화되면 데이터 소스의 새 인스턴스를 요청합니다. 다음 코드 스니펫은 이러한 동작을 보여줍니다.
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 날짜의 조합은 이름과 날짜가 모두 포함된 단일 문자열로 매핑됩니다.
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
동영상
현재 추천 자료가 없습니다.
Google 계정에 로그인해 보세요.