Actualizar Datos Con Room

En este tutorial veremos cómo actualizar datos con Room a través de la anotación @Update.

La anotación @Update nos permite actualizar tanto un solo registro como múltiples a la vez dentro de una transacción. Además podemos actualizar solo las filas necesarias como en la inserción parcial vista anteriormente.


La Anotación @Update

La anotación @Update marca a un método de un DAO como un método de actualización.

Como se decía al inicio, puedes modificar 1 o más filas relacionadas a una clase @Entity.

Por ejemplo, actualizar las filas de nuestra tabla shopping_list desde ShoppingListDao se vería así:

@Update
void updateShoppingList(ShoppingList shoppingList);

@Update
void updateShoppingLists(List<ShoppingList> shoppingLists);

Si no quisiéramos crear toda la entidad para enviarla en actualización, entonces podemos mapear la inserción en un POJO con las columnas necesarias.

Cabe resaltar que hay que especificar la propiedad entity con la clase.

Ejemplo:

En nuestra App de listas de compras necesitamos marcar las listas como favoritas. Esto implica la modificación de la columna is_favorite y last_updated. Basado en esto, creamos la siguiente clase:

public class ShoppingListFavorite {
    public String id;
    @ColumnInfo(name = "is_favorite")
    public boolean favorite;
    @ColumnInfo(name = "last_updated")
    public String lastUpdated;
}

Luego en nuestro DAO añadimos un método de actualización que reciba como parámetro este tipo:

@Update(entity = ShoppingList.class)
void markFavorite(ShoppingListFavorite shoppingList);

Por otro lado, es importante saber que la actualización parcial también puede ser representada con la anotación @Query de la siguiente forma:

@Query("UPDATE shopping_list SET is_favorite = NOT is_favorite WHERE id = :id)
void markFavorite(String id);

Esta variante puede serte de utilidad si no deseas pasar una entidad.


Ejemplo De Actualización De Datos Con Room

Para practicar la actualización implementaremos permitiremos al usuario marcar como favoritas las listas ya sea de forma individual o en una selección múltiple como se ve en el siguiente boceto:

Actualizar Datos Con Room

Puedes descargar el código completo desde el siguiente enlace si deseas guiarte a la par con él:

Codifiquemos la solución.

1. Agregar Columna Para Favoritas

Agrega una campo booleano llamado is_favorite a la entidad ShoppingList con un valor por defecto en 0 (FALSE). Modifica el constructor y agrega un método get respectivamente:

@ColumnInfo(name = "is_favorite", defaultValue = "0")
private final boolean mFavorite;

Ya sabes que al cambiar el esquema debemos subir la versión de la base de datos (o si quieres desinstala la App para seguir con el mismo número), por lo que vamos a ShoppingListDatabase y aumentamos a 3 el valor.

2. Añadir Botón De Favorito

Necesitamos ver el botón de la siguiente forma:

Material Design: Toggle Button

Dirígete al layout shopping_list_item.xml y agrega un CheckButton al costado derecho del layout para representar un toggle button. La idea es que si es favorito se muestre el icono relleno, de lo contrario delineado.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:padding="@dimen/normal_padding"
    android:layout_height="?listPreferredItemHeight">

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/favorite_button"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Lista de ejemplo" />

    <CheckBox
        android:id="@+id/favorite_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        android:button="@drawable/sl_favorite_24"
        app:buttonTint="@color/favorite_color"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> 

Añade los vectores star y star_outlined con forma de estrella desde New > Vector Asset. Y luego añade un selector llamado sl_favorite_24.xml, que use ambos iconos dependiendo del estado:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/ic_star_outline_24"
        android:state_checked="false"
        />
    <item
        android:drawable="@drawable/ic_star_24"
        android:state_checked="true"
        />
    <item android:drawable="@drawable/ic_star_outline_24" />
</selector>

3. Actualizar Modelo De Listas De Compras

Actualmente usamos la clase ShoppingListForList para mostrar los datos necesarios en la lista. Para sostener a la columna is_favorite agregamos un campo equivalente:

public class ShoppingListForList {
    public String id;
    public String name;
    @ColumnInfo(name = "is_favorite")
    public boolean favorite;
}

Luego bindeamos su resultado al view del ítem en el adaptador:

public void bind(ShoppingListForList item) {
    mNameText.setText(item.name);
    mFavoriteButton.setChecked(item.favorite);
}

Si ejecutas verás a todos las listas desmarcadas:

App Android: Listas de compras desmarcadas

También es necesario cambiar los métodos de consulta en el DAO para que consultemos las tres columnas:

@Query("SELECT id, name, is_favorite FROM shopping_list")
LiveData<List<ShoppingListForList>> getAll();

@Query("SELECT id, name, is_favorite FROM shopping_list WHERE category IN(:categories)")
LiveData<List<ShoppingListForList>> getShoppingListsByCategories(List<String> categories);

4. Procesar Evento En Adaptador

Escuchar el clic sobre el botón de favorito requiere que añadamos un nuevo método a la interfaz ItemListener del adaptador, que notifique a MainActivity dicho evento.

interface ItemListener {
    void onClick(ShoppingListForList shoppingList);

    void onFavoriteIconClicked(ShoppingListForList shoppingList);
}

El ViewHolder enlazara la escucha OnClickListener con el método onFavoriteIconClicked():

public class ShoppingListViewHolder extends RecyclerView.ViewHolder {
    private final TextView mNameText;
    private final CheckBox mFavoriteButton;

    public ShoppingListViewHolder(@NonNull View itemView) {
        super(itemView);
        mNameText = itemView.findViewById(R.id.name);
        mFavoriteButton = itemView.findViewById(R.id.favorite_button);

        // Setear eventos
        mFavoriteButton.setOnClickListener(this::manageEvents);
        itemView.setOnClickListener(this::manageEvents);
    }

    private void manageEvents(View view) {
        if (mItemListener != null) {
            ShoppingListForList clickedItem = mShoppingLists.get(getAdapterPosition());

            // Manejar evento de click en Favorito
            if (view.getId() == R.id.favorite_button) {
                mItemListener.onFavoriteIconClicked(clickedItem);
                return;
            }

            mItemListener.onClick(clickedItem);
        }
    }

    public void bind(ShoppingListForList item) {
        mNameText.setText(item.name);
        mFavoriteButton.setChecked(item.favorite);
    }
}

La idea es comunicarle al ViewModel que debe iniciar una actualización de la lista de compras con el Id determinado. El siguiente código muestra como:

// Asignar escucha de ítems
mAdapter.setItemListener(new ShoppingListAdapter.ItemListener() {
    @Override
    public void onClick(ShoppingListForList shoppingList) {
        editShoppingList(shoppingList);
    }

    @Override
    public void onFavoriteIconClicked(ShoppingListForList shoppingList) {
        mViewModel.markFavorite(shoppingList);
    }
});

5. Propagar Acciones Hacia ViewModel Y Repositorio

En ShoppingListViewModel crearemos el método markFavorite() como una envoltura para el repositorio de listas de compras.

public void markFavorite(ShoppingListForList shoppingList) {
      ShoppingListFavorite favorite = new ShoppingListFavorite();
      favorite.id = shoppingList.id;
      favorite.favorite = !shoppingList.favorite;
      favorite.lastUpdated = new SimpleDateFormat(
              "yyyy-MM-dd HH:mm:ss",
              Locale.getDefault())
              .format(new Date());
      mRepository.markFavorite(favorite);
 }

Preparamos uno objeto ShoppingListFavorite con el id, la negación del estado actual de favoritismo y la obtención de la fecha con el formato que tenemos actualmente.

Acto seguido, nos creamos un método equivalente en el repositorio, para llamar el método markFavorite() de ShoppingListDao en un hilo separado:

public void markFavorite(ShoppingListFavorite shoppingLists) {
     ShoppingListDatabase.dbExecutor.execute(
             () -> mShoppingListDao.markFavorite(shoppingLists)
     );
 }

Finalmente, si has seguido todos los pasos anteriores, ejecuta y marca como favoritas las que desees. Verás el siguiente resultado y como estos valores persisten localmente:

Listas de compras favoritas

Siguiente tutorial: Eliminar Datos Con Room

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!