Room — Un Vistazo a los ‘Android Architecture Components’

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.

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

  • Room
  • LiveData
  • Handling Lifecycle
  • ViewModel

Aunque por ahora se encuentra en beta y solo funcionan con Android 3.0 Preview (a la fecha sigue en preview) luce prometedor. 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 "android.arch.persistence.room:runtime:1.0.0-beta2"
kapt "android.arch.persistence.room:compiler:1.0.0-beta2"
testImplementation "android.arch.persistence.room:testing:1.0.0-beta2"
implementation "android.arch.persistence.room:rxjava2:1.0.0-beta2"

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 clases 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 android.arch.persistence.room.Entity
import android.arch.persistence.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 android.arch.persistence.room.*
import io.reactivex.Flowable

@Dao
interface TaskDao {
@Query("SELECT * FROM Task")
fun getAllTasks() : Flowable<List<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 android.arch.persistence.room.Database
import android.arch.persistence.room.RoomDatabase

@Database(entities = arrayOf(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 android.arch.persistence.room.Room
import com.devpicon.android.myarchcomponentssampleapplication.AppDatabase

class MyApplication : Application() {
companion object {
var database : AppDatabase? = null
}

override fun onCreate() {
super.onCreate()
MyApplication.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 InstantTaskExecutorRule. La razón de este cambio es para permitir la ejecución de las operaciones en la base de datos de manera instantánea. También, agregaremos 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.

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"

@JvmField
@Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()

@Before
fun initDb() {
database = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
AppDatabase::class.java)
.allowMainThreadQueries()
.build()
}

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

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).

Referencias