このガイドはページング ライブラリの概要をベースとしており、アプリのアーキテクチャ ニーズを満たすようにアプリのデータ読み込みソリューションをカスタマイズする方法について説明します。
監視可能なリストを作成する
通常、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
@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 に最後に表示されるアイテムを考えた場合、そのアイテムより後にページング ライブラリがあらかじめ取得しようとするアイテムの数。この値はページサイズの数倍の大きさになるはずです。
- プレースホルダの表示: 読み込みがまだ終わっていないリストアイテムのプレースホルダを UI に表示するかどうかを決定します。プレースホルダを使用するメリットとデメリットについて詳しくは、UI にプレースホルダを表示する方法をご覧ください。
ページング ライブラリでアプリのデータベースからリストを読み込むタイミングを詳細に制御する場合は、次のコード スニペットに示すように、カスタムの 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 個のデータアイテムを返すことができます。
データが無効な場合に通知する
ページング ライブラリを使用している場合、テーブルまたは行が最新でなくなると、データレイヤがアプリの他のレイヤに通知します。そのためには、アプリに選択した DataSource
クラスから invalidate()
を呼び出します。
独自のデータソースを作成する
カスタムのローカルデータ ソリューションを使用する、またはデータをネットワークから直接読み込む場合は、DataSource
のいずれかのサブクラスを実装できます。次のコード スニペットは、あるコンサートの開始時間からキーオフされたデータソースを示しています。
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);
}
このカスタマイズされたデータを PagedList
オブジェクトに読み込むには、DataSource.Factory
の具体的なサブクラスを作成します。次のコード スニペットは、上記のコード スニペットで定義したカスタム データソースの新しいインスタンスを生成する方法を示しています。
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();
}
}
データ マッピングを指定する
ページング ライブラリは、DataSource
によって読み込まれたアイテムのアイテムベースおよびページベースの変換をサポートしています。
次のコード スニペットでは、コンサートの名前と開催日の組み合わせが、名前と開催日の両方を含む 1 つの文字列にマッピングされています。
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();
}
}
これは、アイテムが読み込まれた後にアイテムのラップ、変換、準備を行う場合に便利です。この処理は取得エグゼキュータで行われるため、ディスクからの読み取りや別のデータベースに対するクエリなどのコストがかかる可能性がある処理を行うことができます。
フィードバックを送信
以下のリソースを通じてフィードバックやアイデアをお寄せください。
- Issue Tracker
- Google がバグを修正できるよう問題を報告します。
参考情報
ページング ライブラリについて詳しくは、以下のリソースをご覧ください。
サンプル
Codelab
動画
あなたへのおすすめ
Paging 3 に移行する
Discover the latest app development tools, platform updates, training, and documentation for developers across every Android device.
Paging 2 ライブラリの概要
Discover the latest app development tools, platform updates, training, and documentation for developers across every Android device.
ページング リストを表示する
Discover the latest app development tools, platform updates, training, and documentation for developers across every Android device.