web-dev-qa-db-fra.com

Modification de PagedList dans Android Bibliothèque d'architecture de pagination

Je cherche actuellement à intégrer la bibliothèque d'architecture de pagination (version 2.1.0-beta01 au moment de la rédaction) dans mon application. L'un des composants est une liste qui permet à l'utilisateur d'en supprimer des éléments individuels. Cette liste est réservée au réseau et la mise en cache locale avec Room n'a pas de sens.

PagedList est immuable et ne prend pas en charge la modification. J'ai lu qu'avoir une copie de la liste qui est ensuite modifiée et retournée comme la nouvelle est la voie à suivre. La documentation indique la même chose:

Si vous avez des signaux de mise à jour plus granulaires, comme une API réseau signalant une mise à jour à un seul élément de la liste, il est recommandé de charger les données du réseau dans la mémoire. Présentez ensuite ces données à la PagedList via un DataSource qui encapsule un instantané en mémoire. Chaque fois que la copie en mémoire change, invalidez la DataSource précédente et une nouvelle enveloppant le nouvel état de l'instantané peut être créée.

J'ai actuellement l'implémentation de base recommandée pour afficher une liste simple. Mon DataSource ressemble à ceci:

class MyDataSource<SomeItem> : PageKeyedDataSource<Int, SomeItem>() {

    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, SomeItem>) {
        // Simple load from API and notification of `callback`.
    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, SomeItem>) {
        // Simple load from API and notification of `callback`.
    }

    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, SomeItem>) {
        // Simple load from API and notification of `callback`.
    }
}

À quoi ressemblerait une implémentation concrète d'un cache en mémoire (sans salle et sans invalider l'ensemble de données) référencé dans la documentation?

12
rubengees

Vous avez raison en ce qu'un DataSource est destiné à contenir des données immuables. Je pense que c'est parce que la bibliothèque de salle et de pagination essaie d'avoir des décisions de conception plus avisées et de plaider pour des données immuables.

C'est pourquoi dans les documents officiels, ils ont une section pour mettre à jour ou muter votre jeu de données devrait invalider la source de données lorsqu'un tel changement se produit.

Mise à jour des données paginées : si vous avez des signaux de mise à jour plus granulaires, comme une API réseau signalant une mise à jour à un seul élément de la liste, il est recommandé de charger données du réseau dans la mémoire. Présentez ensuite ces données à la PagedList via un DataSource qui encapsule un instantané en mémoire. Chaque fois que la copie en mémoire change, invalidez la DataSource précédente et une nouvelle enveloppant le nouvel état de l'instantané peut être créée.

Source: https://developer.Android.com/reference/Android/Arch/paging/DataSource


Dans cet esprit, je pense qu'il est possible de résoudre le problème que vous avez décrit en quelques étapes.

Ce n'est peut-être pas le moyen le plus propre, car cela implique 2 étapes.

Vous pouvez obtenir une référence à l'instantané que contient la PagedList, qui est un type MutableList. Ensuite, vous pouvez simplement supprimer ou mettre à jour l'élément à l'intérieur de cet instantané, sans invalider la source de données.

La deuxième étape serait alors d'appeler quelque chose comme notifyItemRemoved(index) ou notifyItemChanged(index).

Comme vous ne pouvez pas forcer le DataSource à informer les observateurs du changement, vous devrez le faire manuellement.

pagedList.snapshot().remove(index) // Removes item from the pagedList
adapter.notifyItemRemoved(index) // Triggers recyclerview to redraw/rebind to account for the deleted item.

Il existe peut-être une meilleure solution dans votre DataSource.Factory. Selon les documents officiels, votre DataSource.Factory devrait être celui qui émet un nouveau PagedList une fois les données mises à jour.

Mise à jour des données paginées : pour paginer des données à partir d'une source qui fournit des mises à jour, vous pouvez créer un DataSource.Factory, où chaque DataSource créée est invalidée lorsqu'un une mise à jour de l'ensemble de données se produit qui rend l'instantané actuel non valide. Par exemple, lors de la pagination d'une requête à partir de la base de données, et la table interrogée insère ou supprime des éléments. Vous pouvez également utiliser un DataSource.Factory pour fournir plusieurs versions de listes paginées sur le réseau. Si le rechargement de tout le contenu (par exemple en réponse à une action comme glisser-rafraîchir) est nécessaire pour obtenir une nouvelle version des données, vous pouvez connecter un signal d'actualisation explicite pour appeler invalidate () sur la DataSource actuelle.

Source: https://developer.Android.com/reference/Android/Arch/paging/DataSource

Je n'ai cependant pas trouvé de bonne solution pour cette seconde approche.

4
Felipe Roriz

Si vous souhaitez modifier votre liste sans descendre jusqu'à la couche de données, vous devrez remplacer submitList dans votre adaptateur, puis définir un rappel sur votre objet PagedList. Chaque fois que PagedList change, vous pouvez ensuite copier ces modifications dans votre jeu de données local. Ce n'est pas recommandé mais c'est un hack assez minimal pour se mettre au travail.

Voici un exemple:

class MyListAdapter : PagedListAdapter<MyDataItem, MyViewHolder>(MyDiffCallback()) {

    /**
     * This data set is a bit of a hack -- we are copying everything the PagedList loads into our
     * own list.  That way we can modify it.  The docs say you should go all the way down to the
     * data source, modify it there, and then bubble back up, but I don't think that will actually
     * work for us when the changes are coming from the UI itself.
     */
    private val dataSet = arrayListOf<MyDataItem>()

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        //Forces the next page to load when we reach the bottom of the list
        getItem(position)

        dataSet.getOrNull(position)?.let {
            holder.populateFrom(it)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val view = parent.inflate(R.layout.my_view_holder)
        return MyViewHolder(view)
    }

    class MyDiffCallback : DiffUtil.ItemCallback<MyDataItem>() {

        override fun areItemsTheSame(oldItem: MyDataItem, newItem: MyDataItem) =
                oldItem.id == newItem.id

        override fun areContentsTheSame(oldItem: MyDataItem, newItem: MyDataItem) =
                oldItem == newItem
    }

    override fun submitList(pagedList: PagedList<MyDataItem>?) {
        pagedList?.addWeakCallback(listOf(), object : PagedList.Callback() {
            override fun onChanged(position: Int, count: Int) {
                dataSet.clear()
                dataSet.addAll(pagedList)
            }

            override fun onInserted(position: Int, count: Int) {
                dataSet.clear()
                dataSet.addAll(pagedList)
            }

            override fun onRemoved(position: Int, count: Int) {
                dataSet.clear()
                dataSet.addAll(pagedList)
            }
        })
        super.submitList(pagedList)
    }
}
1
PhillyTheThrilly