ページング データを収集する

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

このガイドはページング ライブラリの概要をベースとしており、アプリのアーキテクチャ ニーズを満たすようにアプリのデータ読み込みソリューションをカスタマイズする方法について説明します。

監視可能なリストを作成する

通常、UI コードでは、アプリの ViewModel 内に存在する LiveData<PagedList> オブジェクト(RxJava2 を使用している場合は、Flowable<PagedList> または Observable<PagedList> オブジェクト)を監視します。この監視可能なオブジェクトにより、アプリのリストデータのプレゼンテーションとコンテンツ間の関係が形成されます。

これらの監視可能な PagedList オブジェクトのいずれかを作成するには、DataSource.Factory のインスタンスを LivePagedListBuilder または RxPagedListBuilder オブジェクトに渡します。DataSource オブジェクトは、単一の PagedList のページを読み込みます。ファクトリ クラスは、コンテンツの更新(データベース テーブルの無効化やネットワークの更新など)に応じて PagedList の新しいインスタンスを作成します。Room 永続ライブラリは、DataSource.Factory オブジェクトを提供できます。また、独自のオブジェクトを作成することもできます。

次のコード スニペットは、DataSource.Factory を提供する Room の機能を使用して、アプリの 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 に最後に表示されるアイテムを考えた場合、そのアイテムより後にページング ライブラリがあらかじめ取得しようとするアイテムの数。この値はページサイズの数倍の大きさになるはずです。
  • プレースホルダの表示: 読み込みがまだ終わっていないリストアイテムのプレースホルダを 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
)

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 個のデータアイテムを返すことができます。

データが無効な場合に通知する

ページング ライブラリを使用している場合、テーブルまたは行が最新でなくなると、データレイヤがアプリの他のレイヤに通知します。そのためには、アプリに選択した DataSource クラスから invalidate() を呼び出します。

独自のデータソースを作成する

カスタムのローカルデータ ソリューションを使用する、またはデータをネットワークから直接読み込む場合は、DataSource のいずれかのサブクラスを実装できます。次のコード スニペットは、あるコンサートの開始時間からキーオフされたデータソースを示しています。

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

このカスタマイズされたデータを PagedList オブジェクトに読み込むには、DataSource.Factory の具体的なサブクラスを作成します。次のコード スニペットは、上記のコード スニペットで定義したカスタム データソースの新しいインスタンスを生成する方法を示しています。

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

データ マッピングを指定する

ページング ライブラリは、DataSource によって読み込まれたアイテムのアイテムベースおよびページベースの変換をサポートしています。

次のコード スニペットでは、コンサートの名前と開催日の組み合わせが、名前と開催日の両方を含む 1 つの文字列にマッピングされています。

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

これは、アイテムが読み込まれた後にアイテムのラップ、変換、準備を行う場合に便利です。この処理は取得エグゼキュータで行われるため、ディスクからの読み取りや別のデータベースに対するクエリなどのコストがかかる可能性がある処理を行うことができます。

フィードバックを送信

以下のリソースを通じてフィードバックやアイデアをお寄せください。

Issue Tracker
Google がバグを修正できるよう問題を報告します。

参考情報

ページング ライブラリについて詳しくは、以下のリソースをご覧ください。

サンプル

Codelab

動画