La información es probablemente el recurso más importante en el que confían los usuarios de las apps. Para los desarrolladores de apps, esa información nos dice quién es el usuario, lo que nos faculta para ofrecer una buena experiencia de usuario (UX). Además, al aplicar reglas de negocio a esta información, definimos el comportamiento que debe tener la app. Esta información puede ser sensible y exponer los datos privados de los usuarios, por lo que es muy importante que la manejemos correctamente para asegurar su integridad, privacidad y correcto almacenamiento. Dentro de este tutorial de Android Room, te mostraremos cómo puedes trabajar con estos datos de forma más sencilla, a la vez que garantizas su integridad y seguridad, todo ello mediante el uso de Room. Room forma parte de los componentes de la arquitectura de Android.
¿Necesitas Room?
Cuando hablamos de almacenar información de forma persistente en Android, la opción más importante y «fácil» es utilizar SQLite plano. Sin embargo, tener una buena implementación de base de datos usando SQLite implica la generación de mucho código que no aporta valor real. La arquitectura que sigue a menudo no es tan limpia o clara como podría ser.
También podrías usar mapeo objeto-relacional, u ORM, pero seguirás necesitando definir y crear manualmente la base de datos, subclasificando SQLiteOpenHelper y creando las clases de contrato.
Entonces, la pregunta es: ¿Hay alguna forma de simplificar todo este proceso? La respuesta es: Sí, la sala es su solución.
Una mirada más cercana a Room y su funcionamiento
Room es una de las herramientas más importantes de los componentes arquitectónicos de Android. Lanzada en el Google I/O 2016, es una poderosa herramienta para almacenar y manipular información en las apps de Android. Proporciona una forma muy sencilla de trabajar con los datos y garantiza siempre su seguridad e integridad.
Room no es un ORM, sino que es toda una librería que nos permite crear y manipular bases de datos SQLite más fácilmente. Mediante anotaciones, podemos definir nuestras bases de datos, tablas y operaciones. Room traducirá automáticamente estas anotaciones en instrucciones/consultas SQLite para realizar las operaciones correspondientes en el motor de la base de datos.
Los tres componentes principales de Room son:
– Entidad: Representa una tabla dentro de la base de datos de la Sala. Debe ser anotado con @Entity.
– DAO: Una interfaz que contiene los métodos para acceder a la base de datos. Se anota con @Dao.
– Base de datos: Representa la base de datos. Es un objeto que mantiene una conexión con la base de datos SQLite, y todas las operaciones se ejecutan a través de ella. Se anota con @Database.
La arquitectura de la sala tiene el siguiente aspecto:
Demasiada palabrería-Veamos un ejemplo
Vamos a sumergirnos en este tutorial de Android Room. Imagina que necesitamos crear una aplicación para almacenar tu rutina de gimnasio. Vamos a tener cuatro entidades en nuestra base de datos, como mostraremos a continuación. Todo el código de ejemplo está escrito usando Kotlin (si no conoces Kotlin o quieres aprender más, te invito a leer mi artículo sobre él).
Lo primero que tenemos que hacer es actualizar nuestro archivo gradle. Debería tener el siguiente aspecto:
apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'apply plugin: 'kotlin-kapt'android { //.. Omitted since it is not relevant for the example}dependencies { //... some dependencies were omitted due to they are not relevant for the example def room_version = "2.2.0-rc01" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" compile 'com.google.code.gson:gson:2.2.4' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation 'io.reactivex.rxjava2:rxjava:2.1.17'}
Veamos y analicemos cada uno de los tres componentes principales de Room: Entidades, DAOs, y Base de Datos.
Entidades
Para nuestro ejemplo, vamos a utilizar cuatro entidades: Género, Ejercicio, Rutina y Aprendiz.
Género.kt
Representa el género del aprendiz
@Entitydata class Gender( @PrimaryKey(autoGenerate = true) val id: Int? = null, val name: String)
Cosas a tener en cuenta:
– Todas las clases que representan una entidad de la base de datos tienen que estar anotadas con @Entity
– Con la anotación @PrimaryKey(autoGenerate = true) estamos indicando que el id es la clave primaria de la entidad y debe ser autoGenerado por el motor de la base de datos.
Ejercicio.kt
Representa un ejercicio que forma parte de una rutina.
@Entitydata class Exercise( @PrimaryKey(autoGenerate = true) val exerciseId: Int, val name: String, val repetitions:Int, @ColumnInfo(name = "machine_name") val machineName: String, val liftedWeight: Int)
Cosas a tener en cuenta:
– Por defecto, Room utiliza los nombres de los campos como nombres de las columnas en la base de datos. Si quieres que una columna tenga un nombre diferente, añade la anotación @ColumnInfo a un campo.
Rutina.kt
Básicamente es un contenedor de ejercicios que juntos crean una rutina de ejercicios.
@Entity(tableName = "traineeRoutine")data class Routine( @PrimaryKey(autoGenerate = true) val routineId: Int, @ColumnInfo(name = "due_day") val dueDay: Date, @TypeConverters(ListConverter::class) val exercises: List)
Cosas a tener en cuenta:
– Cuando una clase está anotada con @Entidad, el nombre de la tabla será el nombre de la clase. Si queremos utilizar un nombre diferente, tenemos que añadir la propiedad tableName junto con la anotación @Entity.
– La anotación @TypeConverters hay que utilizarla cuando declaramos una propiedad cuyo tipo es una clase personalizada, una lista, un tipo de fecha o cualquier otro tipo que Room y SQL no sepan serializar. En este caso, estamos utilizando la anotación a nivel del campo de la clase por lo que sólo ese campo podrá utilizarla. Dependiendo de dónde se coloque la anotación, se comportará de forma diferente como se explica aquí.
Traine.kt
Representa al propietario de la rutina.
@Entity(indices = , foreignKeys = , childColumns = )])data class Trainee( @PrimaryKey(autoGenerate = true) val id: Int, val name: String, val age: Int, val gender: Int?, @Embedded val routine: Routine)
DAOs
Los objetos de acceso a datos (DAOs) se utilizan para acceder a nuestros datos cuando implementamos Room. Cada DAO tiene que incluir un conjunto de métodos para manipular los datos (insertar, actualizar, borrar u obtener).
Un DAO puede ser implementado como una interfaz o como una clase abstracta. En nuestro caso, vamos a utilizar una interfaz. Como todos los DAOs son básicamente idénticos, sólo mostraremos uno.
GenderDao.kt@Daointerface GenderDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertGender(gender: Gender) @Update fun updateGender(gender: Gender) @Delete fun deleteGender(gender: Gender) @Query("SELECT * FROM Gender WHERE name == :name") fun getGenderByName(name: String): List @Query("SELECT * FROM Gender") fun getGenders(): List}
Algunas cosas a tener en cuenta:
– Todos los DAOs tienen que estar anotados con @Dao.
– Una función anotada con @Insert, @Update o @Delete tiene que recibir como parámetro una instancia de la clase deseada, que representa el objeto que queremos insertar, actualizar o borrar respectivamente.
– En el caso de las operaciones de inserción o actualización, podemos utilizar la propiedad onConflict para indicar qué hacer cuando se produce un conflicto realizando la operación. Las estrategias disponibles para utilizar son: REPLACE, ABORT, FAIL, IGNORE y ROLLBACK.
– Si queremos obtener información específica de una o varias entidades, podemos anotar una función con @Query y proporcionar un script SQL como parámetro.
Base de Datos
Representa la base de datos. Mantiene una conexión con la base de datos SQLite real.
AppDatabase.kt@Database(entities = , version = 1)@TypeConverters(DateTypeConverter::class)abstract class AppDatabase : RoomDatabase() { abstract fun exerciseDao(): ExerciseDao abstract fun genderDao(): GenderDao abstract fun routineDao(): RoutineDao abstract fun traineeDao(): TraineeDao companion object { var INSTANCE: AppDatabase? = null fun getAppDataBase(context: Context): AppDatabase? { if (INSTANCE == null){ synchronized(AppDatabase::class){ INSTANCE = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "myDB").build() } } return INSTANCE } fun destroyDataBase(){ INSTANCE = null } }}
Cosas a tener en cuenta aquí:
– Se trata de una clase abstracta que tiene que extender RoomDatabase.
– Tiene que estar anotada con @Database, y recibe una lista de entidades con todas las clases que componen la base de datos (todas estas clases tienen que estar anotadas con @Entity). También tenemos que proporcionar una versión de la base de datos.
– Tenemos que declarar una función abstracta para cada una de las entidades incluidas en la anotación @Database. Esta función tiene que devolver el DAO correspondiente (una clase anotada con @Dao).
– Por último, declaramos un objeto compañero para obtener acceso estático al método getAppDataBase, que nos da una instancia singleton de la base de datos.
Convertidores de tipo
Los convertidores de tipo se utilizan cuando declaramos una propiedad que Room y SQL no saben serializar. Veamos un ejemplo de cómo serializar un tipo de dato ‘date’.
DateTypeConverter.ktclass DateTypeConverter { @TypeConverter fun fromTimestamp(value: Long?): Date? { return if (value == null) null else Date(value) } @TypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time }}
Usando la base de datos de Room
Ahora veamos un ejemplo muy sencillo de cómo utilizar la base de datos de Room que acabamos de crear:
MainActivity.ktclass MainActivity : AppCompatActivity() { private var db: AppDatabase? = null private var genderDao: GenderDao? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Observable.fromCallable({ db = AppDatabase.getAppDataBase(context = this) genderDao = db?.genderDao() var gender1 = Gender(name = "Male") var gender2 = Gender(name = "Female") with(genderDao){ this?.insertGender(gender1) this?.insertGender(gender2) } db?.genderDao()?.getGenders() }).doOnNext({ list -> var finalString = "" list?.map { finalString+= it.name+" - " } tv_message.text = finalString }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe() }}
¿Qué estamos haciendo?
– Obtener la instancia de la base de datos y GenderDao.
– Crear dos instancias de Gender: Masculino y Femenino.
– Insertar las dos instancias creadas en la base de datos.
– Consultar la base de datos para obtener todos los géneros almacenados en ella.
– Fusionar el nombre de todos los géneros que obtuvimos de la base de datos, y establecer el texto del TextView con ese valor.
Room para hacer más con menos
Room es uno de los elementos importantes de los componentes arquitectónicos de Android. Nos da un marco muy robusto para trabajar con la información y persistente, siempre garantizando la seguridad e integridad de los datos. También proporciona facilidad de uso a los desarrolladores, para que puedan escribir código legible y autoexplicativo. Si quieres hacer más con menos código, y además garantizar la seguridad de los datos del usuario, deberías utilizar Room como capa de persistencia en tu app.
¡Y eso es todo! Esto es casi todo lo que necesitas saber para crear y utilizar una base de datos en Android con Room. Consigue el código completo de este proyecto aquí. ¡Gracias por leer este tutorial de Android Room!