Relaciones Muchos A Muchos Con Room

ANUNCIO
Loading...

En este tutorial aprenderás a implementar relaciones muchos a muchos con Room a través de la anotación @Relation.

Recuerda leer el tutorial relaciones uno a muchos para seguir un trayecto secuencial de esta guía de Room.

Implementar Relaciones Muchos A Muchos

Este tipo de relaciones se dan cuando múltiples instancias de una entidad están asociados con múltiples instancias de otra entidad.

Si deseas simplificar la consulta de estas relaciones sin usar queries complejas, entonces puedes implementarla de la siguiente forma:

Paso 1: Crear una entidad asociativa (referencia cruzada) que contenga las claves primarias de las tablas de la relación.

@Entity(primaryKeys = {"aId", "bId"}) public class ABCrossRef { public int aId; public int bId; }
Lenguaje del código: PHP (php)

Esta tabla te resulta de la conversión relacional en una relación muchos a muchos, por lo que le debes dar persistencia en tu base de datos SQLite.

Paso 2: Crea una clase de resultado dependiendo de la dirección de consulta. Es decir, si quieres obtener todas las filas B asociadas a una entidad A, o si quieres obtener todas las filas A asociadas a una entidad B.

public class AWithBs { @Embedded public A a; @Relation( parentColumn = "aId", entityColumn = "bId", associateBy = @Junction(ABCrossRef.class) ) public List<B> bs; } public class BWithAs { @Embedded public B b; @Relation( parentColumn = "bId", entityColumn = "aId", associateBy = @Junction(ABCrossRef.class) ) public List<A> as; }
Lenguaje del código: PHP (php)

Usa la propiedad associateBy para identificar la entidad de asociación con la anotación @Junction.

Paso 3: Agrega un método de consulta al DAO y dependiendo de la dirección de la misma, usa la entidad de relación apropiada como tipo de retorno.

@Transaction @Query("SELECT * FROM a") public List<A> getAWithBs(); @Transaction @Query("SELECT * FROM b") public List<B> getBWithAs();
Lenguaje del código: Java (java)

Ejemplo De Relaciones Muchos A Muchos Con Room

Usaremos la relación entre las tablas shopping_list e items en nuestro ejemplo de App de listas de compras, para ilustrar la implementación de una relación muchos a muchos.

Modelo relacional muchos a muchos con Room

Luego mostraremos los ítems de una lista de compras en un RecyclerView, ubicado en la actividad la pantalla de edición.

Prototipo actividad de edición de lista de compras

Descarga el código completo para tenerlo como referencia desde el siguiente enlace:

1. Crear Tabla Para Ítems

Crea una nueva clase anotada con @Entity cuyo nombre refleje el diagrama de base de datos anterior (item).

@Entity public class Item { @NonNull @PrimaryKey @ColumnInfo(name = "item_id") public String id; @NonNull public String name; public Item(@NonNull String id, @NonNull String name) { this.id = id; this.name = name; } }
Lenguaje del código: Java (java)

2. Crear Tabla Asociativa

Crea otra clase para representar la tabla asociativa shopping_list_item. Como viste en el diagrama, agrega las claves primarias de las tablas involucradas y márcalas como clave primaria compuesta.

@Entity(tableName = "shopping_list_item", primaryKeys = {"shopping_list_id", "item_id"}, foreignKeys = { @ForeignKey( entity = ShoppingList.class, parentColumns = "shopping_list_id", childColumns = "shopping_list_id", onDelete = ForeignKey.CASCADE), @ForeignKey( entity = Item.class, parentColumns = "item_id", childColumns = "item_id") } ) public class ShoppingListItem { @NonNull @ColumnInfo(name = "shopping_list_id") public String shoppingListId; @NonNull @ColumnInfo(name = "item_id") public String itemId; public ShoppingListItem(@NonNull String shoppingListId, @NonNull String itemId) { this.shoppingListId = shoppingListId; this.itemId = itemId; } }
Lenguaje del código: Java (java)

Luego actualiza la base de datos a la versión 6 y agrega ambas entidades.

@Database(entities = { ShoppingList.class, Info.class, Collaborator.class, Item.class, ShoppingListItem.class}, version = 6, exportSchema = false) public abstract class ShoppingListDatabase extends RoomDatabase { }
Lenguaje del código: Java (java)

3. Crear Clase De Resultado

Añade la clase para mapear los resultados obtenidos de la relación.

Recuerda que consultaremos los ítems de las listas de compras, por lo que el campo @Embedded es la lista de compra y el @Relation es la lista de items.

public class ShoppingListWithItems { @Embedded public ShoppingList shoppingList; @Relation( parentColumn = "shopping_list_id", entityColumn = "item_id", associateBy = @Junction(ShoppingListItem.class) ) public List<Item> items; }
Lenguaje del código: PHP (php)

4. Obtener Items De Lista De Compra

Ahora modifica ShoppingListDao para que el método que consultaba el detalle de una lista de compras retorne en ShoppingListWithItems.

@Transaction @Query("SELECT * FROM shopping_list WHERE shopping_list_id = :id") public abstract LiveData<ShoppingListWithItems> shoppingListWithItems(String id);
Lenguaje del código: CSS (css)

Propaga este cambio a los retornos de los métodos del ViewModel y el repositorio.

5. Modificar Layout De Edición De Listas De Compras

Como observaste en el prototipo propuesto, debes agregar un RecyclerView para mostrar los ítems de la lista de compras.

Así que abre activity_edit_shopping_list.xml y pega el siguiente código:

<?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:layout_height="match_parent" tools:context=".editshoppinglist.EditShoppingListActivity"> <androidx.recyclerview.widget.RecyclerView android:layout_width="0dp" android:id="@+id/items_list" android:layout_height="0dp" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" tools:listitem="@layout/list_item" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Lenguaje del código: HTML, XML (xml)

Seguido, crea un layout para el ítem llamado list_item.xml para mostrar el nombre de cada ítem.

<?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:layout_height="?listPreferredItemHeight" android:padding="@dimen/normal_padding"> <TextView android:id="@+id/item_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?textAppearanceBody1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="Espinaca" /> </androidx.constraintlayout.widget.ConstraintLayout>
Lenguaje del código: HTML, XML (xml)

6. Crear Adaptador De Items

Infla la lista de items creando un adaptador que enlace la entidad Item con el TextView del layout para items de lista.

Usa el siguiente código:

public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ItemViewHolder> { private List<Item> mItems; @NonNull @Override public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ItemViewHolder(LayoutInflater.from( parent.getContext()).inflate( R.layout.list_item, parent, false) ); } @Override public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) { Item item = mItems.get(position); holder.bind(item); } @Override public int getItemCount() { return mItems == null ? 0 : mItems.size(); } public void setItems(List<Item> items) { mItems = items; notifyDataSetChanged(); } public static class ItemViewHolder extends RecyclerView.ViewHolder { public TextView mNameText; public ItemViewHolder(@NonNull View itemView) { super(itemView); mNameText = itemView.findViewById(R.id.item_name); } public void bind(Item item) { mNameText.setText(item.name); } } }
Lenguaje del código: Java (java)

7. Cargar Elementos Con El ViewModel

Desde EditShoppingListActivity toma la referencia del recycler view y vincúlalo a una instancia del adaptador.

private void setupItemsList() { mItemsList = findViewById(R.id.items_list); mAdapter = new ItemAdapter(); mItemsList.setAdapter(mAdapter); }
Lenguaje del código: Java (java)

Ahora en la suscripción que hicimos al LiveData con la lista de compras, usa el método setItems() del adaptador para actualizar la lista en cada notificación de cambio.

private void subscribeToUi() { mViewModel.getShoppingList().observe(this, shoppingList -> { mActionBar.setTitle(shoppingList.shoppingList.name); mAdapter.setItems(shoppingList.items); } ); }
Lenguaje del código: Java (java)

8. Crear DAOs

Añade dos nuevos DAOs abstractos para los ítems (ItemDao) y la tabla de referencia (ShoppingListItemDao). Escríbeles un método para insertar una lista de elementos.

@Dao public abstract class ItemDao { @Insert(onConflict = OnConflictStrategy.IGNORE) public abstract void insertAll(List<Item> items); } @Dao public abstract class ShoppingListItemDao { @Insert(onConflict = OnConflictStrategy.IGNORE) public abstract void insertAll(List<ShoppingListItem> shoppingListItems); }
Lenguaje del código: Java (java)

Ahora exponlos desde ShoppingListDatabase con un método get.

public abstract ItemDao itemDao(); public abstract ShoppingListItemDao shoppingListItemDao();
Lenguaje del código: PHP (php)

9. Llamar A Múltiples DAOs En Una Trasacción

Y para terminar. Ya que vas a insertar datos desde múltiples DAOs desde la prepoblación, es necesario que crees una transacción para conservar la atomicidad.

Ve a base de datos e inserta cinco items por cada lista de compras en la prepoblación y luego llama al método RoomDatabase.runInTransaction().

private static void prepopulate(Context context) { // Obtener instancias de Daos ShoppingListDao shoppingListDao = INSTANCE.shoppingListDao(); ItemDao itemDao = INSTANCE.itemDao(); ShoppingListItemDao shoppingListItemDao = INSTANCE.shoppingListItemDao(); List<ShoppingListInsert> lists = new ArrayList<>(); List<Info> infos = new ArrayList<>(); List<Collaborator> collaborators = new ArrayList<>(); List<Item> items = new ArrayList<>(); List<ShoppingListItem> shoppingListItems = new ArrayList<>(); for (int i = 0; i < 5; i++) { String dummyId = String.valueOf((i + 1)); // Crear lista de compras ShoppingListInsert shoppingList = new ShoppingListInsert( dummyId, "Lista " + (i + 1) ); // Crear info String date = Utils.getCurrentDate(); Info info = new Info( shoppingList.id, date, date); // Crear colaborador Collaborator collaborator = new Collaborator(dummyId, "Colaborador " + dummyId, dummyId); // Crear ítems de la lista for (int j = 0; j < 5; j++) { Item item = new Item(dummyId + (j + 1), "Item #" + (j + 1)); // Crear filas de "lista <contiene> item" ShoppingListItem shoppingListItem = new ShoppingListItem(shoppingList.id, item.id); items.add(item); shoppingListItems.add(shoppingListItem); } lists.add(shoppingList); infos.add(info); collaborators.add(collaborator); } // Crear transacción para llamar DAOs getInstance(context).runInTransaction(() -> { shoppingListDao.insertAllWithInfosAndCollaborators(lists, infos, collaborators); itemDao.insertAll(items); shoppingListItemDao.insertAll(shoppingListItems); }); }
Lenguaje del código: Java (java)

Por otro lado, como también insertas una lista de compras desde la actividad AddShoppingListActivity, es necesario que modifiques el método de guardado del repositorio.

Entra a ShoppingListRepository y llama al método runInTransaction() de la base de datos para insertar todos los registros desde los DAOs relacionados en insert().

public void insert(ShoppingListInsert shoppingList, Info info, List<Collaborator> collaborators, List<Item> items) { ShoppingListDatabase.dbExecutor.execute( () -> mDb.runInTransaction( () -> processInsert(shoppingList, info, collaborators, items) ) ); } private void processInsert(ShoppingListInsert shoppingList, Info info, List<Collaborator> collaborators, List<Item> items) { // Insertar lista de compras mShoppingListDao.insertWithInfoAndCollaborators(shoppingList, info, collaborators); // Insertar items mItemDao.insertAll(items); // Generar registros de relación List<ShoppingListItem> shoppingListItems = new ArrayList<>(); for (Item item : items) { shoppingListItems.add(new ShoppingListItem(shoppingList.id, item.id)); } // Insertar registros de relación mShoppingListItemDao.insertAll(shoppingListItems); }
Lenguaje del código: Java (java)

Finalmente, ejecuta el aplicativo y revisa que se carguen los ítems de las listas de compras en la actividad de edición.

Relaciones Muchos A Muchos Con Room

Siguiente tutorial: Crear Vistas En Room

¿Ha sido útil esta publicación?