Room — Un Vistazo a los ‘Android Architecture Components’

Armando Picón
DevPicon by Armando
4 min readOct 16, 2017

--

No hace mucho, durante el último Google I/O hicieron el anuncio sobre el desarrollo de los Architecture Components, un conjunto de bibliotecas que formarían parte de la propuesta del equipo de Android para establecer una arquitectura recomendada para los nuevos desarrollos en Android.

Update al 23 de abril del 2019: He actualizado el repositorio y los ejemplos a las últimas versiones de Room y en general a AndroidX.

Los llamados Architecture Components están conformados por las siguientes bibliotecas o componentes:

  • Room
  • LiveData
  • Handling Lifecycle
  • ViewModel

Hoy en día muchas de las dependencias ya se encuentran en versiones estables. Por ahora le daremos un repaso al primer componente Room.

Room: a SQL object mapping library

Durante mucho tiempo veníamos creando uno conjunto de clases para determinar la estructura de nuestras tablas, las sentencias y operaciones CRUD. Ahora, esos tiempo de ¿sufrimiento? se han terminado.

Aunque la documentación es algo sencilla de entender, de todos modos les dejo los pasos que seguí para empezar a probar este recurso, en Kotlin en este caso porque la red está invadida de ejemplos escritos con Java.

Agregar las dependencias

Tan solo se requiere agregar las dependencias necesarias:

implementation "androidx.room:room-runtime:2.0.0"
kapt "androidx.room:room-compiler:2.0.0"
testImplementation "androidx.room:room-testing:2.0.0"
implementation "androidx.room:room-rxjava2:2.0.0"

compile 'io.reactivex.rxjava2:rxkotlin:2.1.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

Crear POJOS para definir las Entidades

Crear los POJOs agregándole ciertas anotaciones propias de Room. En el ejemplo a continuación estamos definiendo una clase que guardará la información de nuestras tareas, mediante la anotación @Entity Room identificará que dicho POJO es la representación de una entidad en particular, mientras que la anotación @PrimaryKey será la encargada de decirle qué atributo será el que tome el rol de llave primaria de nuestra entidad.

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity
data class Task(@PrimaryKey(autoGenerate = true) val id : Long, val description : String)

Existe la posibilidad de definir llaves primarias compuestas, todas esas particularidades las podrán encontrar directamente en la documentación en la sección Entities.

Definir los DAOs

Luego de ello debemos definir la interfaz de nuestro DAO (Data Access Object) de una forma similar a como aparece a continuación. Esta interfaz deberá llevar en principio la anotación @Dao para que Room la reconozca y luego por cada operación que precisemos definir usaremos la anotación @Query para nuestras funciones de consulta @Insert, @Delete y @Update para el resto de operaciones.

import androidx.room.*
import io.reactivex.Flowable

@Dao
interface TaskDao {
@Query("SELECT * FROM Task")
fun getAllTasks(): Flowable<List<Task>>

@Query("SELECT * FROM Task WHERE description = :description")
fun getTask(description: String): Task
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertTask(task: Task)

@Update
fun updateTask(tasks: Array<Task>)

@Delete
fun deleteTask(tasks: Array<Task>)

}

Aunque esta representación es algo simple, es posible elaborar algunas más complejas incluyendo el envío de parámetros a las consultas. Estas particularidades las podremos encontrar también en la documentación en la sección DAO.

Declarar la base de datos

Finalmente, necesitamos efectuar la declaración de la base de datos; para que se le reconozca como una base de datos que emplea Room.

import androidx.room.Database
import androidx.room.RoomDatabase

@Database(entities = [Task::class],version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun taskDao() : TaskDao
}

Para poder acceder al objeto de la base de datos defino una clase custom application de una forma similar a como lo hago a continuación:

import android.app.Application
import androidx.room.Room
import com.devpicon.android.myarchcomponentssampleapplication.AppDatabase

class MyApplication : Application() {
companion object {
lateinit var database : AppDatabase
}

override fun onCreate() {
super.onCreate()
database = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "sample-db").build()
}
}

¿Cómo testear los DAOs?

Creamos la clase que albergará nuestros tests. Por medio de la función inMemoryDatabaseBuilder podremos generar una instancia de nuestra base de datos que se destruirá una vez que el proceso muera.

Gracias a Florina Muntenescu supe que, adicionalmente, había que agregar la invocación a la función allowMainThreadQueries, para que la ejecución de nuestras consultas en el hilo principal. Les dejo el enlace de su post en la sección de Referencias.

Update al 23 de abril del 2019: Inicialmente el artículo hacía referencia a la necesidad de agregar una instancia de InstantTaskExecutorRule. para asegurarnos que las tareas se ejecuten instantáneamente, pero luego de la migración no le he visto mayor utilidad.

A continuación, les dejaré un ejemplo de cómo escribir el test para el DAO que hemos escrito, el test consistirá en insertar y obtener un elemento a través de las operaciones definidas en nuestro DAO:

@RunWith(AndroidJUnit4::class)
class TaskDaoTest {

private lateinit var database: AppDatabase

private val DESCRIPTION = "Tarea 1"

@Before
fun initDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(context,
AppDatabase::class.java)
.allowMainThreadQueries()
.build()
}

@Test
fun insertAndGetTask() {
// Insert
database.taskDao().insertTask(Task(0, DESCRIPTION))

// Query an specific description
val foundTask: Task = database.taskDao().getTask(DESCRIPTION)
assertNotNull(foundTask)
assertEquals(DESCRIPTION, foundTask.description)

// Query all elements
val allTasks: Flowable<List<Task>> = database.taskDao().getAllTasks()
allTasks.subscribe { tasks ->
run {
tasks.size
assertThat(tasks.size, `is`(1))
val task: Task = tasks[0]
assertEquals(DESCRIPTION, task.description)
}
}
}

@After
fun closeDb() {
database.close()
}
}

El ejemplo completo se encuentra en github, lo iré actualizando para hacerlo un poco más útil (por ahora solo inserta y lee valores desde Room).

Update al 23 de abril del 2019: He actualizado las dependencias, si desean ver las diferencias pueden revisar los commits del repositorio.

Referencias

--

--

Armando Picón
DevPicon by Armando

lifelong learner • 👨🏽‍💻 Android eng (ex: @uber ) • 🇵🇪 @ 🇨🇱 • @gdgopen • content creator @devpicon | @primosauda