JavaでのPagingSourceクラスの作り方
KotlinでのPagingSourceクラスの作り方が一段落したところで、Java版に話を移すことにします。
JavaではListenableFuturePagingSourceを継承
Java版のPhonePagingSourceは、リスト5のコードになります。
public class PhonePagingSource extends ListenableFuturePagingSource<Integer, Phone> { // (1)
@NonNull
@Override
public ListenableFuture<LoadResult<Integer, Phone>> loadFuture(@NonNull LoadParams<Integer> loadParams) { // (2)
LoadResult<Integer, Phone> returnVal; // (3)
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
int startKey = 1;
if(loadParams.getKey() != null) {
startKey = loadParams.getKey();
}
PhoneListSizeFetcher phoneListSizeFetcher = new PhoneListSizeFetcher(); // (4)
Future<Integer> phoneListSizeFuture = executorService.submit(phoneListSizeFetcher); // (5)
int phoneListSize = phoneListSizeFuture.get(); // (5)
int endKey = startKey + loadParams.getLoadSize() - 1;
if(endKey > phoneListSize) {
endKey = phoneListSize;
}
PhoneListFetcher phoneListFetcher = new PhoneListFetcher(startKey, endKey); // (6)
Future<List<Phone>> phoneListFuture = executorService.submit(phoneListFetcher); // (7)
List<Phone> extractedPhoneList = phoneListFuture.get(); // (7)
Integer prevKey = null;
if(startKey - loadParams.getLoadSize() > 0) {
prevKey = startKey - loadParams.getLoadSize();
}
Integer nextKey = null;
if(endKey + 1 < phoneListSize) {
nextKey = endKey + 1;
}
returnVal = new LoadResult.Page<>(extractedPhoneList, prevKey, nextKey); // (8)
}
catch(Exception ex) {
returnVal = new LoadResult.Error<>(ex); // (9)
}
return Futures.immediateFuture(returnVal); // (10)
}
@Nullable
@Override
public Integer getRefreshKey(@NonNull PagingState<Integer, Phone> pagingState) {
Integer returnVal = null;
Integer anchorPosition = pagingState.getAnchorPosition();
if(anchorPosition != null) {
Phone phone = pagingState.closestItemToPosition(anchorPosition);
if(phone != null) {
PagingConfig config = pagingState.getConfig();
int returnKey = phone.id.intValue() - config.pageSize / 2;
if(returnKey > 0) {
returnVal = returnKey;
}
}
}
return returnVal;
}
}
基本的には、KotlinコードをJavaコードに置き換えた内容になっていますが、いくつか注意点があります。
まず、一番の違いは親クラスです。Kotlinではネットアクセスなどの非同期処理にコルーチンを利用できます。そのため、load()メソッドはsuspend関数となっており、内部でのネットアクセス関数も、suspend関数として用意できます。
一方、Javaにはそういった仕組みはありませんが、必ず非同期処理に対応しておく必要があります。そのため、リスト5の(1)のように、親クラスを、PagingSourceではなく、ListenableFutureでラップされたListenableFuturePagingSourceとします。ListenableFuturePagingSourceも、PagingSource同様に抽象クラスであり、抽象メソッドを2個オーバーライドする必要があります。そのうち、getRefreshKey()メソッドは、シグネチャも内部の処理コードもKotlinと変わりません。
loadFuture()の戻り値
一方、もうひとつの抽象メソッドは、リスト5の(2)のように、load()ではなくloadFuture()と名称が変わっています。
さらに、名称が変わっただけではなく、その戻り値もLoadResultからListenableFuture<LoadResult>に、すなわち、LoadResultをListenableFutureでラップする必要があります。これこそが、まさに、Kotlinでのsuspend関数の代わりといえます。
リスト5では、Kotlinコード同様に(3)でLoadResult型のreturnValを用意しておき、tryブロックの最後の(8)で無事リストデータが取得できた場合であるLoadResult.Pageインスタンスを格納します。一方で、catchブロック内の(9)で失敗した場合であるLoadResult.Errorインスタンスを格納しています。
ただし、このreturnValをListenableFutureでラップするために、(10)でFuturesクラスのimmediateFuture()メソッドを利用しています。
immediateFuture()の注意点
このimmediateFuture()を利用することで、簡単に引数をListenableFutureでラップすることができます。ただし、このメソッドを利用する場合は、引数で渡す値が、引数で渡すタイミングで確実に確定されているコードパターンでのみ使用し、immediateFuture()の実行タイミングでは、非同期処理は全て終了していることを前提にしてください。
リスト5では(4)でnewしているPhoneListSizeFetcherクラスは、Callableインターフェースを実装したクラスであり、リスト2の(2)で実行しているsuspend関数であるfetchPhoneListSize()の代わりとなるものです。それを非同期実行しているのが(5)のexecutorService.submit()であり、その結果として取得したFutureオブジェクトであるphoneListSizeFutureのget()メソッドを実行することで、非同期処理が終了するのを待った上で、戻り値の全リストサイズを取得できます。
同様に、(6)でnewしているPhoneListFetcherクラスは、同じく、Callableインターフェースを実装したクラスであり、リスト2の(5)で実行しているsuspend関数であるfetchPhoneList()の代わりとなるものです。その後の(7)のコードは、(5)同様に、PhoneListFetcherを非同期実行した上で、その非同期処理が終了するのを待った上でリストデータを取得することができます。
これらこのコードパターンのおかげで、(7)のコードが終了した時点で非同期処理が解消されていることになり、immediateFuture()が利用しても問題とならないようになります。ただし、この場合、それぞれの非同期処理、すなわち、PhoneListSizeFetcherクラス内処理、および、PhoneListFetcherクラス内処理にそれほど時間がかからないことを前提としています。もし処理時間が長時間になる場合は、(5)のget()、および(7)のget()で処理がブロックされてしまうので注意してください。
まとめ
Android Jetpackについて紹介していく本連載の第13回は、いかがでしたでしょうか。
今回は、ページングライブラリを利用して、ネット上にあるリストデータをページングする方法を紹介しました。次回は、ページングライブラリを利用して、ネット上のリストデータをデータベースにキャッシュして利用する方法を紹介します。
