1. Antes de comenzar
La mayoría de las apps de calidad de producción tienen datos que deben guardarse. Por ejemplo, una app podría almacenar una lista de reproducción de canciones, elementos de una lista de tareas pendientes, registros de gastos e ingresos, un catálogo de constelaciones o un historial de datos personales. En la mayoría de estos casos, se usa una base de datos para almacenar esos datos persistentes.
Room es una biblioteca de persistencias que forma parte de Android Jetpack. Es una capa de abstracción que se ubica sobre una base de datos SQLite. SQLite usa un lenguaje especializado (SQL) para realizar operaciones de bases de datos. En lugar de usar SQLite directamente, Room simplifica las tareas de configuración de la base de datos, así como las interacciones con la app. Room también proporciona verificaciones en tiempo de compilación de las instrucciones de SQLite.
Una capa de abstracción es un conjunto de funciones que ocultan la implementación o la complejidad subyacente. Proporciona una interfaz para un conjunto existente de funciones, como SQLite en este caso.
En la siguiente imagen, se puede apreciar el modo en que Room, como fuente de datos, se adapta a la arquitectura general recomendada en este curso. Room es una fuente de datos.

Requisitos previos
- Saber compilar una interfaz de usuario (IU) básica de una app para Android con Jetpack Compose
- Saber usar elementos componibles como
Text,Icon,IconButtonyLazyColumn - Saber usar el elemento componible
NavHostpara definir rutas y pantallas en tu app - Saber navegar entre pantallas con un
NavHostController - Conocer el componente de la arquitectura de Android
ViewModely saber usarViewModelProvider.Factorypara crear una instancia de ViewModels - Conocer los conceptos básicos de simultaneidad
- Saber usar corrutinas para tareas de larga duración
- Conocimientos básicos sobre las bases de datos SQLite y el lenguaje SQL
Qué aprenderás
- Cómo crear la base de datos SQLite y cómo interactuar con ella mediante la biblioteca Room
- Cómo crear una entidad, un objeto de acceso a datos (DAO) y clases de bases de datos
- Cómo usar un DAO para asignar funciones de Kotlin a consultas en SQL
Qué compilarás
- Compilarás una app de Inventory que guarde elementos de inventario en la base de datos SQLite.
Qué necesitas
- El código de partida de la app de Inventory
- Una computadora con Android Studio
- Un dispositivo o un emulador con nivel de API 26 o posterior
2. Descripción general de la app
En este codelab, trabajarás con un código de partida de la app de Inventory y le agregarás la capa de la base de datos con la biblioteca de Room. La versión final de la app mostrará una lista de elementos de la base de datos de inventario. El usuario tendrá opciones para agregar un elemento nuevo, actualizar uno existente y borrarlo de la base de datos de inventario. En este codelab, guardarás los datos del elemento en la base de datos de Room. Completarás el resto de la funcionalidad de la app en el siguiente codelab.
|
|
|
3. Descripción general de la app de partida
Descarga el código de partida para este codelab
Para comenzar, descarga el código de partida:
Como alternativa, puedes clonar el repositorio de GitHub para el código:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-inventory-app.git $ cd basic-android-kotlin-compose-training-inventory-app $ git checkout starter
Puedes explorar el código en el repositorio de GitHub de Inventory app.
Descripción general del código de partida
- Abre el proyecto con el código de partida en Android Studio.
- Ejecuta la app en un dispositivo Android o en un emulador. Asegúrate de que el emulador o dispositivo conectado ejecute un nivel de API 26 o uno superior. El Inspector de bases de datos funciona en emuladores y dispositivos que ejecutan el nivel de API 26 o uno posterior.
- Observa que la app no muestra datos de inventario.
- Presiona el botón de acción flotante (BAF), que te permite agregar elementos nuevos a la base de datos.
La app navega a una pantalla nueva en la que puedes ingresar los detalles del elemento nuevo.
|
|
Problemas con el código de partida
- En la pantalla Add Item, ingresa los detalles de un elemento, como el nombre, el precio y la cantidad.
- Presiona Guardar. La pantalla Add Item no se cierra, pero puedes navegar hacia atrás con la tecla de volver. La función de guardar no está implementada, por lo que no se guardan los detalles del elemento.
Ten en cuenta que la app está incompleta y no se implementa la funcionalidad del botón Save.

En este codelab, agregarás el código que usa Room para guardar los detalles del inventario en la base de datos SQLite. Usas la biblioteca de persistencias Room para interactuar con la base de datos SQLite.
Explicación del código
El código de partida que descargaste tiene diseños de pantalla prediseñados. En esta ruta de aprendizaje, te enfocarás en implementar la lógica de la base de datos. La siguiente sección es una breve explicación de algunos de los archivos para comenzar.
ui/home/HomeScreen.kt
Este archivo es la pantalla principal o la primera pantalla de la app, que contiene los elementos componibles para mostrar la lista de inventario. Tiene un BAF
para agregar elementos nuevos a la lista. Mostrarás los elementos de la lista más adelante en la ruta de aprendizaje.

ui/item/ItemEntryScreen.kt
Esta pantalla es similar a ItemEditScreen.kt. Ambos tienen campos de texto para los detalles del elemento. Esta pantalla se muestra cuando se presiona el BAF en la pantalla principal. ItemEntryViewModel.kt es el ViewModel correspondiente de esta pantalla.

ui/navigation/InventoryNavGraph.kt
Este archivo es el gráfico de navegación de toda la aplicación.
4. Componentes principales de Room
Kotlin ofrece una manera fácil de trabajar con datos a través de clases de datos. Si bien es fácil trabajar con datos en la memoria usando clases de datos, cuando se trata de datos persistentes, debes convertirlos en un formato compatible con el almacenamiento de bases de datos. De este modo, necesitas tablas para almacenar los datos y consultas para acceder a ellos y modificarlos.
Los siguientes tres componentes de Room facilitan estos flujos de trabajo.
- Las entidades de Room representan tablas de la base de datos de tu app. Se usan para actualizar los datos almacenados en filas de las tablas y crear filas nuevas para insertarlas.
- Los DAOs de Room proporcionan métodos que tu app usa para recuperar, actualizar, insertar y borrar datos en la base de datos.
- La clase de Database de Room es la clase de base de datos que proporciona a tu app instancias de los DAO asociados con esa base de datos.
Más adelante en este codelab, implementarás estos componentes y aprenderás más sobre ellos. En el siguiente diagrama, se muestra cómo los componentes de Room funcionan en conjunto para interactuar con la base de datos.

Agrega dependencias de Room
En esta tarea, agregarás las bibliotecas de componentes de Room necesarias a tus archivos Gradle.
- Abre el archivo de Gradle de nivel de módulo
build.gradle.kts (Module: InventoryApp.app). - En el bloque
dependencies, agrega las dependencias para la biblioteca Room que se muestra en el siguiente código.
//Room
implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
ksp("androidx.room:room-compiler:${rootProject.extra["room_version"]}")
implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
KSP es una API simple y potente para analizar anotaciones de Kotlin.
5. Crea un elemento Entity
Una clase Entity define una tabla, y cada instancia de esta clase representa una fila en la tabla de la base de datos. Asimismo, tiene asignaciones para indicarle a Room cómo pretende presentar la información en la base de datos e interactuar con ella. En tu app, la entidad conserva información sobre los elementos del inventario, como el nombre, el precio y la cantidad disponible.

La anotación @Entity marca una clase como una clase Entity de base de datos. Para cada clase Entity, la app crea una tabla de base de datos que contenga los elementos. Cada campo de Entity se representa como una columna en la base de datos, a menos que se indique lo contrario (consulta la documentación sobre Entity para obtener más información). Cada instancia de entidad que se almacena en la base de datos debe tener una clave primaria. La clave primaria se usa para identificar de manera única cada registro o entrada en las tablas de tu base de datos. Una vez que la app asigna una clave primaria, no se puede modificar. Representa el objeto de la entidad, siempre que exista en la base de datos.
En esta tarea, crearás una clase Entity y definirás campos para almacenar la siguiente información de inventario para cada elemento: Int para almacenar la clave primaria, String para almacenar el nombre del elemento, double para almacenar el precio del elemento y Int para almacenar la cantidad en stock.
- Abre el código de partida en Android Studio.
- Abre el paquete
dataen el paquete basecom.example.inventory. - Dentro del paquete
data, abre la clase de KotlinItem, que representa una entidad de base de datos en tu app.
// No need to copy over, this is part of the starter code
class Item(
val id: Int,
val name: String,
val price: Double,
val quantity: Int
)
Clases de datos
Las clases de datos se usan principalmente para conservar datos en Kotlin. Se definen con la palabra clave data. Los objetos de clase de datos de Kotlin tienen algunos beneficios adicionales. Por ejemplo, el compilador genera automáticamente utilidades para comparar, imprimir y copiar elementos como toString(), copy() y equals().
Ejemplo:
// Example data class with 2 properties.
data class User(val firstName: String, val lastName: String){
}
Para garantizar la coherencia y el comportamiento significativo del código generado, las clases de datos deben cumplir con los siguientes requisitos:
- El constructor principal debe tener al menos un parámetro.
- Todos los parámetros del constructor principal deben ser
valovar. - Las clases de datos no pueden ser
abstract,opennisealed.
Para obtener más información sobre las clases de datos, consulta la documentación correspondiente.
- Prefija la definición de la clase
Itemcon la palabra clavedatapara convertirla en una clase de datos.
data class Item(
val id: Int,
val name: String,
val price: Double,
val quantity: Int
)
- Sobre la declaración de clase
Item, anota la clase de datos con@Entity. Usa el argumentotableNamepara estableceritemscomo el nombre de la tabla de SQLite.
import androidx.room.Entity
@Entity(tableName = "items")
data class Item(
...
)
- Anota la propiedad
idcon@PrimaryKeypara queidsea la clave primaria. Una clave primaria es un ID para identificar de manera única cada registro o entrada en la tablaItem.
import androidx.room.PrimaryKey
@Entity(tableName = "items")
data class Item(
@PrimaryKey
val id: Int,
...
)
- Asigna a
idun valor predeterminado de0, que es necesario para queidgenere automáticamente valores deid. - Agrega el parámetro
autoGeneratea la anotación@PrimaryKeypara especificar si la columna de clave primaria se debe generar de forma automática. SiautoGenerateestá configurado comotrue, Room generará automáticamente un valor único para la columna de clave primaria cuando se inserte una nueva instancia de entidad en la base de datos. Esto garantiza que cada instancia de la entidad tenga un identificador único, sin tener que asignar valores manualmente a la columna de clave primaria.
data class Item(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
// ...
)
¡Excelente! Ahora que creaste una clase Entity, puedes crear un objeto de acceso a datos (DAO) para acceder a la base de datos.
6. Crea el elemento DAO
El objeto de acceso a datos (DAO) es un patrón que puedes usar para separar la capa de persistencia del resto de la aplicación proporcionando una interfaz abstracta. Este aislamiento sigue el principio de responsabilidad única, que viste en los codelabs anteriores.
La funcionalidad del DAO es ocultar todas las complejidades relacionadas con la realización de operaciones de la base de datos en la capa de persistencia, aparte del resto de la aplicación. Esto te permite cambiar la capa de datos independientemente del código que usa los datos.

En esta tarea, definirás un DAO para Room. Los DAO son los componentes principales de Room que son responsables de definir la interfaz que accede a la base de datos.
El DAO que creas es una interfaz personalizada que proporciona métodos convenientes para consultar/recuperar, insertar, borrar y actualizar la base de datos. Room genera una implementación de esta clase en el tiempo de compilación.
La biblioteca de Room proporciona anotaciones de conveniencia, como @Insert, @Delete y @Update, para definir métodos que realizan inserciones, actualizaciones y eliminaciones simples sin necesidad de escribir una instrucción de SQL.
Si necesitas definir operaciones más complejas para la inserción, actualización o eliminación, o si necesitas consultar los datos en la base de datos, usa una anotación @Query.
Como beneficio adicional, a medida que escribes tus consultas en Android Studio, el compilador comprueba si las consultas de SQL tienen errores de sintaxis.
En el caso de la app de Inventory, debes poder hacer lo siguiente:
- Insertar o agregar un elemento nuevo
- Actualizar un elemento existente para actualizar el nombre, el precio y la cantidad
- Obtener un elemento específico según su clave primaria,
id - Obtener todos los elementos para que puedas mostrarlos
- Borrar una entrada de la base de datos

Completa los siguientes pasos para implementar el elemento DAO en tu app:
- En el paquete
data, crea la interfaz de KotlinItemDao.kt.

- Anota la interfaz
ItemDaocon@Dao.
import androidx.room.Dao
@Dao
interface ItemDao {
}
- Dentro del cuerpo de la interfaz, agrega una anotación
@Insert. - Debajo de
@Insert, agrega una funcióninsert()que tome una instancia delitemde la claseEntitycomo su argumento. - Marca la función con la palabra clave
suspendpara permitir que se ejecute en un subproceso separado.
Las operaciones de la base de datos pueden demorar mucho tiempo en ejecutarse, por lo que deben hacerlo en un subproceso independiente. Room no permite el acceso a la base de datos en el subproceso principal.
import androidx.room.Insert
@Insert
suspend fun insert(item: Item)
Cuando se insertan elementos en la base de datos, se pueden generar conflictos. Por ejemplo, varios lugares en el código intentan actualizar la entidad con valores diferentes, en conflicto, como la misma clave primaria. Una entidad es una fila en DB. En la app de Inventory, solo insertamos la entidad desde un lugar que es la pantalla Agregar elemento, por lo que no esperamos que haya ningún conflicto y podemos establecer la estrategia de conflicto como Ignorar.
- Agrega un argumento
onConflicty asígnale un valor deOnConflictStrategy.IGNORE.
El argumento onConflict le indica a Room qué hacer en caso de conflicto. La estrategia OnConflictStrategy.IGNORE ignora un elemento nuevo.
Para obtener más información sobre las estrategias de conflicto disponibles, consulta la documentación de OnConflictStrategy.
import androidx.room.OnConflictStrategy
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
Ahora, Room genera todo el código necesario para insertar item en la base de datos. Cuando llamas a cualquiera de las funciones DAO que están marcadas con anotaciones de Room, Room ejecuta la consulta en SQL correspondiente en la base de datos. Por ejemplo, cuando llamas al método anterior, insert() desde tu código Kotlin, Room ejecuta una consulta en SQL para insertar la entidad en la base de datos.
- Agrega una función nueva con la anotación
@Updateque tome unItemcomo parámetro.
La entidad que se actualiza tiene la misma clave primaria que la que se pasa. Puedes actualizar algunas o todas las demás propiedades de la entidad.
- Al igual que con el método
insert(), marca esta función con la palabra clavesuspend.
import androidx.room.Update
@Update
suspend fun update(item: Item)
Agrega otra función con la anotación @Delete para borrar elementos y convertirla en una función de suspensión.
import androidx.room.Delete
@Delete
suspend fun delete(item: Item)
No hay ninguna anotación de conveniencia para la funcionalidad restante, por lo que debes usar la anotación @Query y proporcionar consultas de SQLite.
- Escribe una consulta de SQLite para recuperar un elemento específico de la tabla de elementos según el
idespecificado. El siguiente código proporciona una consulta de muestra que selecciona todas las columnas deitems, dondeidcoincide con un valor específico yides un identificador único.
Ejemplo:
// Example, no need to copy over
SELECT * from items WHERE id = 1
- Agrega una anotación
@Query. - Usa la consulta de SQLite del paso anterior como un parámetro de cadena a la anotación
@Query. - Agrega un parámetro
Stringa@Query, que es una consulta de SQLite para recuperar un elemento de la tabla correspondiente.
La consulta ahora indica que se seleccionen todas las columnas de items, donde id coincide con el argumento :id. Observa que :id usa la notación de dos puntos en la consulta para hacer referencia a argumentos en la función.
@Query("SELECT * from items WHERE id = :id")
- Después de la anotación
@Query, agrega una funcióngetItem()que tome un argumentoInty muestre unFlow<Item>.
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Query("SELECT * from items WHERE id = :id")
fun getItem(id: Int): Flow<Item>
Se recomienda usar Flow en la capa de persistencia. Con Flow como el tipo de datos que se muestra, recibirás una notificación cada vez que cambien los datos de la base de datos. Room mantiene este Flow actualizado por ti, lo que significa que solo necesitas obtener los datos de forma explícita una vez. Esta configuración es útil para actualizar la lista de inventario, que implementarás en el siguiente codelab. Debido al tipo de datos que se muestra para Flow, Room también ejecuta la búsqueda en el subproceso en segundo plano. No necesitas convertirla de manera explícita en una función suspend ni llamar dentro del alcance de la corrutina.
- Agrega una
@Querycon una funcióngetAllItems(). - Haz que la consulta de SQLite muestre todas las columnas de la tabla
item, ordenadas de forma ascendente. - Haz que
getAllItems()muestre una lista de entidadesItemcomoFlow.Roommantiene esteFlowactualizado por ti, lo que significa que solo necesitas obtener los datos de forma explícita una vez.
@Query("SELECT * from items ORDER BY name ASC")
fun getAllItems(): Flow<List<Item>>
ItemDao completado:
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
@Dao
interface ItemDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
@Update
suspend fun update(item: Item)
@Delete
suspend fun delete(item: Item)
@Query("SELECT * from items WHERE id = :id")
fun getItem(id: Int): Flow<Item>
@Query("SELECT * from items ORDER BY name ASC")
fun getAllItems(): Flow<List<Item>>
}
- Si bien no notarás ningún cambio visible, compila la app para asegurarte de que no tenga errores.
7. Crea una instancia de base de datos
En esta tarea, crearás una RoomDatabase que use tu Entity y tu DAO a partir de las tareas anteriores. La clase de base de datos define la lista de entidades y DAO.
La clase Database proporciona a tu app instancias de los DAO que definas. A su vez, la app puede usar los DAO para recuperar datos de la base de datos como instancias de objetos de entidad de datos asociados. La app también puede usar las entidades de datos definidas para actualizar filas de las tablas correspondientes o crear filas nuevas para su inserción.
Debes crear una clase abstracta RoomDatabase y anotarla con @Database. Esta clase tiene un método que muestra la instancia existente de RoomDatabase si la base de datos no existe.
Este es el proceso general para obtener la instancia RoomDatabase:
- Crea una clase
public abstractque extiendaRoomDatabase. La nueva clase abstracta que defines actúa como un contenedor de la base de datos. La clase que defines es abstracta porqueRoomcrea la implementación por ti. - Anota la clase con
@Database. En los argumentos, enumera las entidades para la base de datos y establece el número de versión. - Define una propiedad o un método abstracto que muestre una instancia de
ItemDao, yRoomgenera la implementación por ti. - Solo necesitas una instancia de
RoomDatabasepara toda la app, así que haz queRoomDatabasesea un singleton. - Usa el
Room.databaseBuilderdeRoompara crear tu base de datos (item_database), solo si no existe. De lo contrario, muestra la base de datos existente.
Crea la base de datos
- En el paquete
data, crea una clase de KotlinInventoryDatabase.kt. - En el archivo
InventoryDatabase.kt, haz que la claseInventoryDatabasesea una claseabstractque extiendaRoomDatabase. - Anota la clase con
@Database. Ignora el error de parámetros faltantes, ya que lo corregirás en el siguiente paso.
import androidx.room.Database
import androidx.room.RoomDatabase
@Database
abstract class InventoryDatabase : RoomDatabase() {}
La anotación @Database requiere varios argumentos para que Room pueda compilar la base de datos.
- Especifica el
Itemcomo la única clase con la lista deentities. - Establece
versioncomo1. Cada vez que cambies el esquema de la tabla de la base de datos, debes aumentar el número de versión. - Establece
exportSchemacomofalsepara que no se conserven las copias de seguridad del historial de versiones de esquemas.
@Database(entities = [Item::class], version = 1, exportSchema = false)
- Dentro del cuerpo de la clase, declara una función abstracta que muestre el
ItemDaode modo que la base de datos sepa sobre el DAO.
abstract fun itemDao(): ItemDao
- Debajo de la función abstracta, define un
companion object, que permite el acceso a los métodos para crear u obtener la base de datos y usa el nombre de clase como calificador.
companion object {}
- Dentro del objeto
companion, declara una variable anulable privadaInstancepara la base de datos y, luego, inicialízala ennull.
La variable Instance conserva una referencia a la base de datos, cuando se crea una. Esto ayuda a mantener una sola instancia de la base de datos abierta en un momento determinado, que es un recurso costoso para crear y mantener.
- Anota
Instancecon@Volatile.
El valor de una variable volátil nun


