En este tutorial aprenderás a cómo usar la anotación @DatabaseView
para crear vistas en Room con el fin de empaquetar consultas complejas.
Recuerda leer el tutorial de relaciones muchos a muchos para que sigas la secuencia de esta guía de Room.
Vistas En SQLite
Las vistas son comandos SELECT
empaquetados bajo un nombre dado, que proveen acceso directo a un conjunto de datos de interés.
Las usamos para:
- Obtener información con estructura amigable
- Ocultar los detalles del esquema de la base de datos
- Resumir reportes de consultas que impliquen la cláusula
JOIN
La librería Room te permite asociar clases de resultados a la creación de una vista para que puedas consultarlas a través de DAOs.
Para crear una vista, añade una nueva clase que represente el resultado y anótala con @DatabaseView
. Usa el parámetro value
para establecer comando SELECT
asociado y viewName
si deseas establecer un nombre particular para la vista
@DatabaseView(
value=
"SELECT columna1, columna2, ... " +
"FROM nombre_tabla " +
"WHERE [condicion]", viewName="example_view")
public class ViewExample {
public tipo column1;
public tipo column2;
...
}
Luego inclúyela en tu base de datos con la propiedad views
de la anotación @Entity
.
@Database(entities = {Example.class}, views = {ViewExample.class},
version = 1)
public abstract class ExampleDatabase extends RoomDatabase {
public abstract ExampleDao exampleDao();
}
En el momento en que declares la sentencia SELECT
haz que coincidan los nombres de los campos de la clase con los nombres de la consulta.
Usa la anotación @ColumnInfo
si deseas nombrar el atributo con otra convención.
Ejemplo De Vistas En Room
Tomemos la actividad con la listas de compras de nuestro App de ejemplo para añadir una vista a la base de datos.
Con esta vista mostraremos todas las columnas de la lista de compras que ya obteníamos junto a la cantidad de ítems que contiene.
El siguiente es el código de SQLite puro de la vista.
SELECT
l.*,
i.created_date,
COUNT(*) as itemsCount
FROM
shopping_list l
INNER JOIN
info i USING(shopping_list_id)
INNER JOIN
shopping_list_item USING(shopping_list_id)
INNER JOIN
item USING(item_id)
GROUP BY
l.shopping_list_id
Recuerda que USING
equivale a ON tabla1.id_tabla1 = tabla2.id_tabla1
si ambas columnas de comparación se llaman igual.
Puedes descargar el proyecto Android Studio desde el siguiente enlace:
1. Crear Vista En La Base De Datos
Crea una nueva clase llamada ShoppingListView
la cual use como sentencia de selección la consulta de todas las listas de compras y el conteo de sus ítems.
@DatabaseView(
value = "SELECT l.*, i.created_date, COUNT(*) as itemsCount " +
"FROM shopping_list l " +
"INNER JOIN info i " +
"USING(shopping_list_id) " +
"INNER JOIN shopping_list_item " +
"USING(shopping_list_id) " +
"INNER JOIN item " +
"USING(item_id)" +
"GROUP BY l.shopping_list_id",
viewName = "v_full_shopping_lists"
)
public class ShoppingListView {
@ColumnInfo(name = "shopping_list_id")
public String id;
public String name;
public String category;
@ColumnInfo(name = "is_favorite")
public boolean favorite;
@ColumnInfo(name = "created_date")
public String createdDate;
public int itemsCount;
}
Inmediatamente regístrala en ShoppingListDatabase
y sube la versión a 7
.
@Database(entities = {
ShoppingList.class,
Info.class,
Collaborator.class,
Item.class,
ShoppingListItem.class},
views = ShoppingListView.class,
version = 7, exportSchema = false)
public abstract class ShoppingListDatabase extends RoomDatabase {
}
2. Consultar Vista Con DAO
Al interior de ShoppingListDao
modifica a los dos métodos que retornan las listas de compras para el adaptador (consulta general y filtro categorías).
La idea es que sus anotaciones @Query
consulten a la vista que creaste.
@Transaction
@Query("SELECT * FROM v_full_shopping_lists")
public abstract LiveData<List<ShoppingListWithCollaborators>> shoppingLists();
@Transaction
@Query("SELECT * FROM v_full_shopping_lists WHERE category IN(:categories)")
public abstract LiveData<List<ShoppingListWithCollaborators>> getShoppingListsByCategories(List<String> categories);
3. Actualizar Clase De Resultados
Ya que cambiarás la proyección de la consulta de listas de compras, es necesario que modifiques la clase ShoppingListWithCollaborators
. Usa la vista como entidad padre y tan solo deja a la tabla de colaboradores.
public class ShoppingListWithCollaborators {
@Embedded
public ShoppingListView shoppingList;
@Relation(
entity = Collaborator.class,
parentColumn = "shopping_list_id",
entityColumn = "shopping_list_id",
projection = {"name"}
)
public List<String> collaboratorNames;
}
Con esto obtendrás toda la información necesaria en el diseño de MainActivity
.
4. Modificar Layout De Listas De Compras
El cambio es mínimo. Agrega un TextView
que represente la cantidad de ítems en el extremo inferior derecho.
Usa la siguiente definición XML:
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="144dp"
android:padding="@dimen/normal_padding">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceHeadline6"
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"
app:layout_constraintVertical_bias="0.0"
tools:text="Lista de ejemplo" />
<TextView
android:id="@+id/created_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?textAppearanceCaption"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/name"
app:layout_constraintVertical_bias="0.0"
tools:text="26/05/2020 01:12:54" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/favorite_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:button="@drawable/sl_favorite_24"
android:minWidth="0dp"
android:minHeight="0dp"
app:buttonTint="@color/favorite_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/delete_button"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<ImageView
android:id="@+id/delete_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/ic_delete_24" />
<TextView
android:id="@+id/collaborators_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/collaborators_label"
android:textAllCaps="true"
android:textAppearance="?textAppearanceCaption"
app:layout_constraintBottom_toTopOf="@+id/collaborator_names"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/created_date"
app:layout_constraintVertical_bias="1.0" />
<TextView
android:id="@+id/collaborator_names"
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"
tools:text="Cesar, Ramiro, Cristina" />
<TextView
android:id="@+id/items_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
android:textAppearance="?textAppearanceHeadline4"
tools:text="5" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
Y en consecuencia, abre el adaptador y bindea el nuevo valor.
public class ShoppingListViewHolder extends RecyclerView.ViewHolder {
private final TextView mNameText;
private final CheckBox mFavoriteButton;
private final ImageView mDeleteButton;
private final TextView mCreatedDateText;
private final TextView mCollaboratorsText;
private final TextView mItemsCount;
public ShoppingListViewHolder(@NonNull View itemView) {
super(itemView);
mNameText = itemView.findViewById(R.id.name);
mCreatedDateText = itemView.findViewById(R.id.created_date);
mFavoriteButton = itemView.findViewById(R.id.favorite_button);
mDeleteButton = itemView.findViewById(R.id.delete_button);
mCollaboratorsText = itemView.findViewById(R.id.collaborator_names);
mItemsCount = itemView.findViewById(R.id.items_count);
// Setear eventos
mFavoriteButton.setOnClickListener(this::manageEvents);
mDeleteButton.setOnClickListener(this::manageEvents);
itemView.setOnClickListener(this::manageEvents);
}
private void manageEvents(View view) {
if (mItemListener != null) {
ShoppingListWithCollaborators clickedItem = mShoppingLists.get(getAdapterPosition());
// Manejar evento de click en Favorito
if (view.getId() == R.id.favorite_button) {
mItemListener.onFavoriteIconClicked(clickedItem);
return;
} else if (view.getId() == R.id.delete_button) {
mItemListener.onDeleteIconClicked(clickedItem);
return;
}
mItemListener.onClick(clickedItem);
}
}
public void bind(ShoppingListWithCollaborators item) {
mNameText.setText(item.shoppingList.name);
mFavoriteButton.setChecked(item.shoppingList.favorite);
mCreatedDateText.setText(item.shoppingList.createdDate);
mCollaboratorsText.setText(TextUtils.join(",", item.collaboratorNames));
mItemsCount.setText(String.valueOf(item.shoppingList.itemsCount));
}
}
Con esos cambios ya puedes ejecutar el proyecto y ver la consulta de la view creada en la base de datos.
Siguiente Tutorial: Database Inspector