web-dev-qa-db-ja.com

PagedListAdapterは、新しいPagedListを受け取るとリストの先頭にジャンプします

Paging Libraryを使用して、ItemKeyedDataSourceを使用してネットワークからデータをロードしています。ユーザーが編集できるアイテムを取得した後、この更新はMemoryキャッシュ内で行われます(Roomなどのデータベースは使用されません)。

PagedList自体は更新できないため(議論 ここ )、PagedListを再作成してPagedListAdapterに渡す必要があります。

更新自体は問題ありませんが、recyclerViewを新しいPagedListで更新した後、リストはリストの先頭にジャンプし、previousスクロールを破棄しますポジション。 Roomでの動作など、スクロール位置を維持しながらPagedListを更新する方法はありますか?

DataSourceは次のように実装されます:

public class MentionKeyedDataSource extends ItemKeyedDataSource<Long, Mention> {

    private Repository repository;
    ...
    private List<Mention> cachedItems;

    public MentionKeyedDataSource(Repository repository, ..., List<Mention> cachedItems){
        super();

        this.repository = repository;
        this.teamId = teamId;
        this.inboxId = inboxId;
        this.filter = filter;
        this.cachedItems = new ArrayList<>(cachedItems);
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Long> params, final @NonNull ItemKeyedDataSource.LoadInitialCallback<Mention> callback) {
        Observable.just(cachedItems)
                .filter(() -> return cachedItems != null && !cachedItems.isEmpty())
                .switchIfEmpty(repository.getItems(..., params.requestedLoadSize).map(...))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> callback.onResult(response.data.list));
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Long> params, final @NonNull ItemKeyedDataSource.LoadCallback<Mention> callback) {
        repository.getOlderItems(..., params.key, params.requestedLoadSize)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> callback.onResult(response.data.list));
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Long> params, final @NonNull ItemKeyedDataSource.LoadCallback<Mention> callback) {
        repository.getNewerItems(..., params.key, params.requestedLoadSize)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> callback.onResult(response.data.list));
    }

    @NonNull
    @Override
    public Long getKey(@NonNull Mention item) {
        return item.id;
    }
}

PagedListは次のように作成されます:

PagedList.Config config = new PagedList.Config.Builder()
        .setPageSize(PAGE_SIZE)
        .setInitialLoadSizeHint(preFetchedItems != null && !preFetchedItems.isEmpty()
                ? preFetchedItems.size()
                : PAGE_SIZE * 2
        ).build();

pagedMentionsList = new PagedList.Builder<>(new MentionKeyedDataSource(mRepository, team.id, inbox.id, mCurrentFilter, preFetchedItems)
        , config)
        .setFetchExecutor(ApplicationThreadPool.getBackgroundThreadExecutor())
        .setNotifyExecutor(ApplicationThreadPool.getUIThreadExecutor())
        .build();

PagedListAdapterは次のように作成されます:

public class ItemAdapter extends PagedListAdapter<Item, ItemAdapter.ItemHolder> { //Adapter from google guide, Nothing special here.. }

mAdapter = new ItemAdapter(new DiffUtil.ItemCallback<Mention>() {
            @Override
            public boolean areItemsTheSame(Item oldItem, Item newItem) {
                return oldItem.id == newItem.id;
            }

            @Override
            public boolean areContentsTheSame(Item oldItem, Item newItem) {
                return oldItem.equals(newItem);
            }
        });

、次のように更新されます:

mAdapter.submitList(pagedList);
11
Keivan Esbati

loadInitialの実装で_androidx.paging.ItemKeyedDataSource.LoadInitialParams#requestedInitialKey_を使用しておらず、使用する必要があると思います。

自動生成されたRoom DAOコードItemKeyedDataSourceで使用されるLimitOffsetDataSourceの別の実装を調べました。 loadInitialの実装には、次のものが含まれます( Apache 2.0ライセンス コードが続きます):

// bound the size requested, based on known count final int firstLoadPosition = computeInitialLoadPosition(params, totalCount); final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount);

...これらの関数は_params.requestedStartPosition_、_params.requestedLoadSize_および_params.pageSize_を使用して何かを実行します。

何が問題なのでしょうか?

新しいPagedListを渡すときは常に、ユーザーが現在スクロールしている要素が含まれていることを確認する必要があります。それ以外の場合、PagedListAdapterはこれをこれらの要素の削除として扱います。その後、loadAfterまたはloadBeforeアイテムがこれらの要素をロードすると、これらの要素の後続の挿入としてそれらが処理されます。表示されているアイテムの削除と挿入を行わないようにする必要があります。一番上までスクロールしているように聞こえるため、誤ってすべてのアイテムを削除してすべて挿入している可能性があります。

PagedListsでRoomを使用するときにこれが機能すると私が考える方法は次のとおりです。

  1. データベースが更新されます。
  2. ルームオブザーバーはデータソースを無効にします。
  3. PagedListAdapterコードは無効化を特定し、ファクトリを使用して新しいデータソースを作成し、_params.requestedStartPosition_を可視要素に設定してloadInitialを呼び出します。
  4. 新しいPagedListがPagedListAdapterに提供されます。Pag​​edListAdapterは、差分チェックコードを実行して、実際に何が変更されたかを確認します。通常、表示されているものに変更はありませんが、要素が挿入、変更、または削除された可能性があります。初期ロード以外のすべてのものは削除されたものとして扱われます-これはUIでは目立たないはずです。
  5. スクロールするとき、PagedListAdapterコードは、新しい項目をロードする必要があることを特定し、loadBeforeまたはloadAfterを呼び出すことができます。
  6. これらが完了すると、新しいPagedList全体がPagedListAdapterに提供されます。Pag​​edListAdapterは、差分チェックコードを実行して、実際に何が変更されたかを確認します。通常-単なる挿入。

それがあなたがしようとしていることにどのように対応するのかはわかりませんが、多分それは役に立ちますか?新しいPagedListを提供するときは常に、以前のリストと比較され、偽の挿入や削除がないことを確認する必要があります。そうしないと、混乱する可能性があります。

その他のアイデア

PAGE_SIZEが十分に大きくない問題も確認しました。ドキュメントでは、一度に表示できる要素の最大数の数倍を推奨しています。

1
Chrispher