이제 두 번째 Android 11 개발자 프리뷰를 사용할 수 있습니다. 테스트해 보고 의견을 공유하세요.

페이징 데이터 수집

이 가이드에서는 페이징 라이브러리 개요를 기반으로 앱의 아키텍처 요구에 맞게 앱의 데이터 로드 솔루션을 맞춤설정하는 방법을 설명합니다.

식별 가능한 목록 구성

일반적으로 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>
    }
    

자바

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

자바

    // 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에서 마지막으로 표시되는 항목이 있다고 한다면 마지막 항목 이후에 페이징 라이브러리가 미리 가져오려고 시도해야 하는 항목 수입니다. 이 값은 페이지 크기보다 몇 배 더 커야 합니다.
  • 자리표시자 있음: UI에서 로드가 아직 완료되지 않은 목록 항목의 자리표시자를 표시할지 여부를 결정합니다. 자리표시자 사용의 이점과 단점에 관한 자세한 내용은 UI에서 자리표시자 제공 방법을 참초하세요.

페이징 라이브러리가 앱의 데이터베이스에서 목록을 로드할 때 더 세밀하게 제어하려면 다음 코드 스니펫에서와 같이 맞춤 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
    )
    

자바

    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개의 데이터 항목을 반환할 수 있습니다.

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

페이징 라이브러리를 사용 중이라면 테이블 또는 행이 오래되었을 때 앱의 다른 레이어에 알릴지는 데이터 영역에 따라 달라집니다. 알리도록 설정하려면 앱과 관련하여 선택한 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();
        }
    }
    

데이터 매핑 제공

페이징 라이브러리는 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
버그를 수정할 수 있도록 문제를 신고해 주세요.

참고 자료

페이징 라이브러리에 관해 자세히 알아보려면 다음 자료를 참조하세요.

샘플

Codelab

동영상