APPRNDとPREPEND両方向の読み込みに対応したRemoteMediatorの実装
おそらくRemoteMediatorを実装したことのある方なら共感していただけると思うのですが、
RemoteMediatorの設定で最も苦労する点は読み込み終了条件の設定方法です。
Android公式のPaging3チュートリアルでは、RemoteMediatorを使う際に読み込み終了条件の設定に
val page = when (loadType) {
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
// If remoteKeys is null, that means the refresh result is not in the database yet.
// We can return Success with endOfPaginationReached = false because Paging
// will call this method again if RemoteKeys becomes non-null.
// If remoteKeys is NOT NULL but its prevKey is null, that means we've reached
// the end of pagination for append.
val nextKey = remoteKeys?.nextKey
if (nextKey == null) {
return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
nextKey
}
...
}
と、MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
と設定しています。
初期ロードページ1固定で下向きのページングしか場合はこの実装方法で何も問題がないのですが、両方向にページングする場合はこの方法だとうまくいきません。というのも、MediatorResult.Success
が返された瞬間APPRNDとPREPEND両方の読み込みが終了してしまうからですね。
この公式の「リモートキーで読み込む」のところが非常に参考になりました。
以下、改良したコードです。
private ~~~API networkService;
private Integer loadKey;
private UserDao userDao;
private RemoteKeysDao keysDao;
private ExecutorService bgExecutor;
private ListenableFuture<MediatorResult> networkResult;
private ListenableFuture<MediatorResult> ioCatchingNetworkResult;
private ListenableFuture<RemoteKeys> keysFuture;
private Integer initialKey;
@NonNull
@Override
public ListenableFuture<MediatorResult> loadFuture(@NonNull LoadType loadType, @NonNull PagingState<Integer, IndicateAuthenticationRecordsUser> pagingState) {
switch (loadType) {
case REFRESH:
if (initialKey != null) {
loadKey = initialKey;
} else {
loadKey = 1;
}
keysFuture = keysDao.getRemoteKeyFuture(-1);
// REFRESH時は必ずMediatorResult.Success(false)が返すのでページ数としてありえないマイナス1を指定しています。
break;
case PREPEND:
if(pagingState.firstItemOrNull() == null){
return Futures.immediateFuture(new MediatorResult.Success(false));
}
keysFuture = keysDao.getRemoteKeyFuture(pagingState.firstItemOrNull().getId());
break;
case APPEND:
if(pagingState.lastItemOrNull() == null){
return Futures.immediateFuture(new MediatorResult.Success(false));
}
keysFuture = keysDao.getRemoteKeyFuture(pagingState.lastItemOrNull().getId());
break;
}
return Futures.transformAsync(keysFuture, remoteKey -> {
switch (loadType) {
case REFRESH:
break;
case PREPEND:
if(remoteKey==null){
return Futures.immediateFuture(new MediatorResult.Success(false));
}
loadKey = remoteKey.getPrevKey();
if(loadKey==null){
return Futures.immediateFuture(new MediatorResult.Success(true));
}
break;
case APPEND:
if(remoteKey==null){
return Futures.immediateFuture(new MediatorResult.Success(false));
}
loadKey = remoteKey.getNextKey();
if(loadKey==null){
return Futures.immediateFuture(new MediatorResult.Success(true));
}
break;
}
networkResult = Futures.transform(
networkService.getAPImethod(),
response -> {
try {
database.runInTransaction(() -> {
List<User> userList = new ArrayList<>();
List<RemoteKeys> keyList = new ArrayList<>();
if (loadType == LoadType.REFRESH) {
userDao.clearAll();
keysDao.clearKeys();
}
// userDao insert method
// keysDao insert method
return new MediatorResult.Success(false);
} catch (Exception e) {
return new MediatorResult.Error(e);
}
}, bgExecutor);
ioCatchingNetworkResult =
Futures.catching(
networkResult,
IOException.class,
MediatorResult.Error::new,
bgExecutor
);
return Futures.catching(
ioCatchingNetworkResult,
HttpException.class,
MediatorResult.Error::new,
bgExecutor
);
}, bgExecutor);
}
ポイントは、networkResultのところでMediatorResult.Success(true);
を返り値の条件に設定すると前後の読み込みの方向に関わらずが終わってしまうため、常にMediatorResult.Success(fasle);
を返している点です。ただし、これだけだと無限読み込みになってしまうので読み込み終了条件を設定してやらねばなりません。そこで、
if(loadKey==null){
return Futures.immediateFuture(new MediatorResult.Success(true));
}
と設定しています。こうすることでPREPEND
とAPPEND
で読み込み終了条件を区別することができます。