페이징 데이터 수집

이 가이드에서는 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

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>
}

Java

@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

Kotlin

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

val concertList = myConcertDataSource.toLiveData(pageSize = 50)

Java

// 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

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
)

Java

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)
    }
}

Java

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
    }
}

Java

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()
}

Java

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)
        }
}

Java

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

동영상