BasicNavigationDrawer + ViewModel + Livedata + ListAdapter

En esta entrada vamos a partir de la segunda aplicación en BasicNavigationDrawer + ViewModel + LiveData a la que vamos a añadirle:

  • Un Fragment para ver y añadir Ingredientes para nuestra aplicación de ejemplo. En este Fragment tendremos un RecyclerView con ListAdapter. A este fragment de Ingredientes accederemos desde el fragment de preferencias.
  • Una clase Ingrediente con métodos que harán que su uso sea cómodo.
  • Un ViewModel para guardar y compartir los datos específicos de los Ingredientes, realmente guardaremos como dato del ViewModel una List<Ingredientes>, es decir una lista de objetos ingredientes.
  • Crearemos el ListAdapter para gestionar los datos para el RecyclerView. En este Adapter crearemos su ViewHolder para crear los layouts de cada ingrediente.

Lo básico de un ListAdapter

Un ListAdapter es una clase base RecyclerView.Adapter para presentar una List (un objeto List, lista de datos) en un RecyclerView, incluyendo la detección de diferencias entre listas que se ejecuta en un background thread (en otro hilo).

El funcionamiento es que el ListAdapter tiene una currentList que compara con la que le pasamos (una nueva lista), utilizando un comparador específico del tipo de dato de los elementos de la lista. Cuando encuentra diferencias, hay nuevos o se han quitado elementos en la nueva lista respecto a la currentList, el sabe (y lo hace sólo) cómo quitar y añadir elementos en la lista, además con animaciones en la inserción y eliminación de elementos. Estas animaciones incluso las podríamos definir nosótros, pero las por defecto nos valen.

Nuestro IngredientesViewModel

Como el adapter va a trabajar con una List, entonces nuestro ViewModel va a alojar una List<Ingrediente>, es decir una Lista de objetos Ingrediente. La clase Ingrediente la definimos posteriormente.

Además como queremos observar la lista en sí (no a los elementos de ella) por si ha cambiado (se han añadido o quitado elementos), la declaramos como MutableLiveData<>, es decir lo embebemos (metemos) en un objeto MutableLiveData.

En nuestro IngredientesViewModel tendremos pues nuestro dato _theList. Recordar que para acceder a un dato embebido en un MutableLiveData ( o un LiveData) tenemos que hacerlo a traves de los métodos getValue y setValue del LiveData.

private MutableLiveData<List<Ingrediente>> _theList;

La clase Ingrediente

Nuestra clase Ingrediente tendrá el siguiente esquema:

public class Ingrediente implements Comparable<Ingrediente> {
    private String strIngrediente;

    public Ingrediente(String strIngrediente) {
        this.strIngrediente = strIngrediente;
    }

    @NonNull
    @Override
    public String toString() {

        return strIngrediente;
    }

    @Override
    public int compareTo(Ingrediente ingrediente) {
        return strIngrediente.compareTo(ingrediente.toString());
    }

    public void setName(String strIngrediente) {
        this.strIngrediente = strIngrediente;
    }

    public static DiffUtil.ItemCallback<Ingrediente> ingredienteDiffCallback = new DiffUtil.ItemCallback<Ingrediente>() {
        @Override
        public boolean areItemsTheSame(@NonNull Ingrediente oldItem, @NonNull Ingrediente newItem) {
            return oldItem.toString().equals(newItem.toString());
        }

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

}

El único dato de la clase es el nombre del ingrediente (strIngrediente)

Un constructor al que se le pasa el nombre del ingrediente

Un conversor de Objeto Ingrediente a Strring, un ToString() que devuelve el nombre

Un método para dar valor al nombre del ingrediente (o cambiarlo), setName(String)

Y un objeto callback de la clase DiffUtil.ItemCallback<T> (donde en nuestro caso T es Ingrediente), que es static (para acceder a él sin instanciar la clase) que va a permitir, gracias a sus dos métodos determinar cuando un Ingrediente (oldItem) es el mismo que otro (newItem) y cuándo un Ingrediente tiene los mismos datos que otro. En nuestro caso siempre los dos métodos son lo mismo puesto que sólo tenemos un dato en la clase. La diferencia entre ellos para clases más grandes es que areItemsTheSame lo usaremos para identificar si son el mismo objeto, basándonos en un miembro id por ejemplo, mientras que areContentsTheSame nos dirá si todos los miembros de ambos ingredientes son iguales.

Este objeto lo va a usar el ListAdapter para realizar las comparaciones de las listas, ya que quién sabe si un objeto es igual a otro es el propio objeto, no el adapter.

El código

El código completo de la versión que vamos a construir lo podéis obtener de GitHub