APPRNDとPREPEND両方向の読み込みに対応したRemoteMediatorの実装

# APPRNDとPREPEND両方向の読み込みに対応したRemoteMediatorの実装

https://developer.android.com/codelabs/android-paging#4

おそらくRemoteMediatorを実装したことのある方なら共感していただけると思うのですが、
RemoteMediatorの設定で最も苦労する点は読み込み終了条件の設定方法です。
Android公式のPaging3チュートリアルでは、RemoteMediatorを使う際に読み込み終了条件の設定に

```18. LoadType に基づいてページを取得する
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両方の読み込みが終了してしまうからですね。

https://developer.android.com/topic/libraries/architecture/paging/v3-network-db#guava-livedata

この公式の「リモートキーで読み込む」のところが非常に参考になりました。
以下、改良したコードです。

```ruby:RemoteMediator.java
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);`を返している点です。ただし、これだけだと無限読み込みになってしまうので読み込み終了条件を設定してやらねばなりません。そこで、
```ruby:読み込み終了条件
if(loadKey==null){
   return Futures.immediateFuture(new MediatorResult.Success(true));
}
```
と設定しています。こうすることで`PREPEND`と`APPEND`で読み込み終了条件を区別することができます。