web-dev-qa-db-fra.com

PagedListAdapter saute au début de la liste lors de la réception de la nouvelle PagedList

J'utilise la bibliothèque de pagination pour charger les données du réseau en utilisant ItemKeyedDataSource. Après avoir récupéré les éléments, l'utilisateur peut les modifier, ces mises à jour sont effectuées à l'intérieur du cache de la mémoire (aucune base de données comme Room n'est utilisée).

Maintenant que le PagedList lui-même ne peut pas être mis à jour (discuté ici ) je dois recréer PagedList et le passer au PagedListAdapter.

La mise à jour elle-même n'est pas un problème mais après avoir mis à jour le recyclerView avec le nouveau PagedList, la liste saute au début de la liste détruisant précédente position de défilement. Est-il possible de mettre à jour PagedList tout en conservant la position de défilement (comme la façon dont cela fonctionne avec Room )?

DataSource est implémenté de cette façon:

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;
    }
}

La PagedList créée comme ceci:

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();

Le PagedListAdapter est créé comme ceci:

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);
            }
        });

, et mis à jour comme ceci:

mAdapter.submitList(pagedList);
11
Keivan Esbati

Vous n'utilisez pas androidx.paging.ItemKeyedDataSource.LoadInitialParams#requestedInitialKey Dans votre implémentation de loadInitial, et je pense que vous devriez l'être.

J'ai jeté un coup d'oeil à une autre implémentation de ItemKeyedDataSource, celle utilisée par le code DAO Room généré automatiquement: LimitOffsetDataSource. Son implémentation de loadInitial contient ( sous licence Apache 2. le code suit):

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

... où ces fonctions font quelque chose avec params.requestedStartPosition, params.requestedLoadSize et params.pageSize.

Alors qu'est-ce qui ne va pas?

Chaque fois que vous passez un nouveau PagedList, vous devez vous assurer qu'il contient les éléments vers lesquels l'utilisateur est actuellement fait défiler. Sinon, votre PagedListAdapter traitera cela comme une suppression de ces éléments. Ensuite, plus tard, lorsque vos éléments loadAfter ou loadBefore chargent ces éléments, il les traite comme une insertion ultérieure de ces éléments. Vous devez éviter d'effectuer cette suppression et cette insertion d'éléments visibles. Comme il semble que vous faites défiler vers le haut, vous supprimez peut-être accidentellement tous les éléments et les insérez tous.

La façon dont je pense que cela fonctionne lorsque vous utilisez Room with PagedLists est la suivante:

  1. La base de données est mise à jour.
  2. Un observateur de salle invalide la source de données.
  3. Le code PagedListAdapter détecte l'invalidation et utilise la fabrique pour créer une nouvelle source de données, et appelle loadInitial avec params.requestedStartPosition Défini sur un élément visible.
  4. Un nouveau PagedList est fourni au PagedListAdapter, qui exécute le code de vérification des différences pour voir ce qui a réellement changé. Habituellement, rien n'a changé pour ce qui est visible, mais peut-être qu'un élément a été inséré, modifié ou supprimé. Tout ce qui est en dehors de la charge initiale est traité comme étant supprimé - cela ne devrait pas être visible dans l'interface utilisateur.
  5. Lors du défilement, le code PagedListAdapter peut détecter que de nouveaux éléments doivent être chargés et appeler loadBefore ou loadAfter.
  6. Une fois ceux-ci terminés, une nouvelle PagedList entière est fournie au PagedListAdapter, qui exécute le code de vérification des différences pour voir ce qui a réellement changé. Habituellement - juste une insertion.

Je ne sais pas comment cela correspond à ce que vous essayez de faire, mais peut-être que cela aide? Chaque fois que vous fournissez une nouvelle PagedList, elle sera différente de la précédente, et vous voulez vous assurer qu'il n'y a pas d'insertions ou de suppressions parasites, ou cela peut devenir vraiment confus.

Autres idées

J'ai également vu des problèmes où PAGE_SIZE n'est pas assez grand. Les documents recommandent plusieurs fois le nombre maximal d'éléments pouvant être visibles à la fois.

1
Chrispher