Room es una biblioteca de persistencia que proporciona una capa abstracta a través de SQLite. Esto permite un acceso fácil y seguro sin tener que renunciar a la función SQLite. En este tutorial aprenderá cómo crear una base de datos y cómo consultar y editar datos de la base de datos.
¿Por qué usar Room y no sólo SQLite directamente?
Con Room usted tiene la verificación del código SQL durante la compilación. Esto no está disponible con SQLite. Además, si modifica el esquema de la base de datos, todas las consultas SQL afectadas también deben modificarse y ajustarse manualmente.
Room es una librería de Mapeo Relacional de Objetos que conecta objetos Java con objetos de la base de datos, haciendo el acceso mucho más rápido y fácil. Los principales componentes de Room son, por ejemplo, "Entities", "DAO", etc., que también se pueden encontrar en programas Java con Hibernate. La documentación oficial de Room se encuentra en esta página web: https://developer.android.com/topic/libraries/architecture/room.html
Para este tutorial necesita Android Studio con un SDK instalado (de Android 5). Está programado con Java. Se crean la base de datos y los métodos necesarios para almacenar los datos de una aplicación de hoja de ruta.
Configuración de condiciones previas
Se necesitan nuevas dependencias para configurar la base de datos. Room y RxJava2 deben ser instalados ahora. Añada esto a la sección "dependencies" en el archivo gradle de la aplicación (el archivo "build.gradle (Module: app)" - localizado bajo la sección Gradle Scripts) en el proyecto recién creado. La última versión de Room se puede encontrar en esta página web: https://developer.android.com/jetpack/androidx/releases/room
La última versión de RxJava: https://github.com/ReactiveX/RxJava/tree/2.x
Estas dependencias son necesarias. Este tutorial utiliza la versión "Room" 2.1.0-rc01, la versión "RxJava" 2.1.0 y la versión "RxAndroid" 2.0.1:
implementation 'android.arch.persistence.room:runtime:2.1.0-rc01'
implementation 'android.arch.persistence.room:rxjava2:2.1.0-rc01'
annotationProcessor 'android.arch.persistence.room:compiler:2.1.0-rc01'
implementation 'io.reactivex.rxjava2:rxjava:2.1.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
Por favor, inicie la sincronización de Gradle para descargar las nuevas dependencias.
Crear modelo de base de datos
Ahora se crean las tablas para la base de datos. Se crea un nuevo "model" de paquete y las tablas se guardan en él.
Una tabla corresponde a una clase Java y a una entidad que contiene anotaciones. La clase Java es "public".
Aquí se crea la clase "Task" de Java, que almacena las tareas.
@Entity(tableName = "task")
public class Task {
@NonNull
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name="id")
private int id;
@NonNull
@ColumnInfo(name="taskname")
private String taskName;
@ColumnInfo(name="duedate")
private Date dueDate;
@ColumnInfo(name="priority")
private int priority;
public Task(){
}
@Ignore
public Task(int id, @NonNull String taskName, Date dueDate, String category, int priority, int points) {
this.id = id;
this.taskName = taskName;
this.dueDate = dueDate;
this.priority = priority;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NonNull
public String getTaskName() {
return taskName;
}
public void setTaskName(@NonNull String taskName) {
this.taskName = taskName;
}
public Date getDueDate() {
return dueDate;
}
public void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
Las columnas de la tabla se definen utilizando variables de instancia. Aquí se crean las columnas "id", "taskname", "duedate" y "priority".
Importante: Los métodos get....() y set...() son necesarios para todas las variables de instancia.
También se debe crear un constructor vacío ("Task()"). También se pueden añadir otros constructores. Aquí se creó un segundo constructor, que sólo es utilizado por nosotros y no por la "Room library".
@Entity: Defina esta clase Java como una tabla. Con tableName se define un nombre de la tabla, que también puede diferir del nombre de la clase Java.
@ColumnInfo: Añada un nombre para las columnas. También puede tener un nombre completamente diferente al de la variable de instancia.
@PrimaryKey: Definir la clave primaria de la base de datos. autoGenerate crea automáticamente una clave primaria para el registro.
@NonNull: Cuando una columna necesita un valor añadido. No olvides añadir esto a la llave primaria.
@Ignore: El código marcado con esta anotación es ignorado por Room y luego no es procesado. Esto es útil, por ejemplo, si un objeto no debe almacenarse en la base de datos.
Opcionalmente un método toString() puede ser añadido a esta clase. Esto permite dar salida a las variables de instancia de la clase cuando se llama el objeto de clase directamente.
Crear "DAO Interface"
Ahora tiene que crear una clase Java del tipo "Interface". La clase debe tener la anotación "@Dao". Las funcionalidades que se pueden implementar posteriormente (actualizar, añadir o borrar datos) se crean como métodos de interfaz en esta interfaz DAO. Además, aquí se definen los métodos para recuperar datos en la base de datos.
@Dao
public interface AppDAO {
@Query("Select * from task where id=:id")
Flowable<Task> getTaskById(int id);
@Query("Select * from task")
Flowable<List<Task>> getAllTasks();
@Insert
void insertTask(Task... tasks);
@Update
void updateTask(Task... tasks);
@Delete
void deleteTask(Task task);
@Query("Delete from task")
void deleteAllTasks();
}
Sobre el método de la interfaz, se utilizan anotaciones para definir cómo acceder a la base de datos.
@Dao - Definir la clase como interfaz DAO.
@Insert - Este método agregará registros. Esto significa que se ejecutará una consulta del tipo "Insertar".
@Update - Este método actualizará los registros.
@Delete - Este método para borrar un registro
@Query - Define las operaciones de la base de datos que se realizarán directamente usando comandos SQL.
Crear base de datos
Ahora cree una clase abstracta ("public abstract class") que debe heredar de la clase RoomDatabase ("extends", clase existente de Room). Esta clase abstracta crea la base de datos de esta aplicación.
@Database(version = 1, entities = {Task.class})
public abstract class AppDatabase extends RoomDatabase {
//Nombre de la base de datos a crear
public static final String DATABASE_NAME ="Tasks-App-Database";
public abstract AppDAO appDAO();
//La instancia de la base de datos
private static AppDatabase dbInstance;
public static AppDatabase getInstance(Context context){
if (dbInstance == null){
dbInstance = Room.databaseBuilder(context,AppDatabase.class,DATABASE_NAME).
fallbackToDestructiveMigration().
build();
}
return dbInstance;
}
}
La clase debe tener una anotación @Database y en esta anotación se añaden las clases de entidad correspondientes a una tabla. Por supuesto, se pueden añadir varias clases de entidades directamente, pero están separadas por comas.
fallbackToDestructiveMigration() - Esta función hace que la base de datos sea recreada cuando se crea un nuevo esquema de base de datos.
Crear funciones (métodos) para la base de datos
Ahora se implementan los métodos creados en la interfaz DAO. Con estos métodos es posible acceder a la base de datos a través de código Java. Ahora se creará una interfaz y dos clases que implementarán esta interfaz ("implements").
1. Se debe crear una clase de interfase con las variables y los métodos de interfase de la clase de interfase DAO AppDAO.
public interface AppDataSourceInterface {
Flowable<Task> getTaskById(int id);
Flowable<List<Task>> getAllTasks();
void insertTask(Task... tasks);
void updateTask(Task... tasks);
void deleteTask(Task task);
void deleteAllTasks();
}
2. Ahora se crea una clase que implementa los métodos de la clase de interfaz creada anteriormente.
public class AppDataSource implements AppDataSourceInterface {
private AppDAO appDAO;
private static AppDataSource dbInstance;
@Override
public Flowable<Task> getTaskById(int id) {
return appDAO.getTaskById(id);
}
@Override
public Flowable<List<Task>> getAllTasks() {
return appDAO.getAllTasks();
}
@Override
public void insertTask(Task... tasks) {
appDAO.insertTask(tasks);
}
@Override
public void updateTask(Task... tasks) {
appDAO.updateTask(tasks);
}
@Override
public void deleteTask(Task task) {
appDAO.deleteTask(task);
}
@Override
public void deleteAllTasks() {
appDAO.deleteAllTasks();
}
}
En los métodos implementados, los métodos correspondientes son llamados por la interfaz DAO appDAO.
3. Ahora se crea una clase que se utiliza como repositorio. Esta clase recién creada también implementa la interfaz "AppDataSourceInterface".
public class AppRepository implements AppDataSourceInterface {
private AppDataSourceInterface dbLocalDataSource;
private static AppRepository dbInstance;
public AppRepository(AppDataSourceInterface dbLocalDataSource){
this.dbLocalDataSource = dbLocalDataSource
}
public static AppRepository getInstance(AppDataSourceInterface dbLocalDataSource){
if (dbInstance == null){
dbInstance = new AppRepository(dbLocalDataSource);
}
return dbInstance;
}
@Override
public Flowable<Task> getTaskById(int id) {
return dbLocalDataSource.getTaskById(id);
}
@Override
public Flowable<List<Task>> getAllTasks() {
return dbLocalDataSource.getAllTasks();
}
@Override
public void insertTask(Task... tasks) {
dbLocalDataSource.insertTask(tasks);
}
@Override
public void updateTask(Task... tasks) {
dbLocalDataSource.updateTask(tasks);
}
@Override
public void deleteTask(Task task) {
dbLocalDataSource.deleteTask(task);
}
@Override
public void deleteAllTasks() {
dbLocalDataSource.deleteAllTasks();
}
}
Esta clase se utiliza para procesar los datos en la base de datos utilizando código Java (por ejemplo, en la clase MainActivity.java).
Tratamiento de los datos en la base de datos
Los métodos creados anteriormente pueden utilizarse ahora, por ejemplo, para añadir, editar, etc. datos.
En MainActivity.Java se creará una lista y un ArrayAdapter. La lista consiste en objetos de clase de la clase "Task" y almacena todas las tareas en una "Array List". Estos objetos Java se utilizan para consultar y editar la base de datos de datos.
List<Task> taskList;
ArrayAdapter adapter;
private CompositeDisposable compositeDisposable;
private AppRepository appRepository;
Los datos se cargan desde la base de datos utilizando un objeto del tipo Disposable. Un desechable envía datos que luego son procesados por los suscriptores. Un objeto desechable consiste en uno o más suscriptores. "Disposable" también se utiliza para todas las demás actividades (añadir datos a la base de datos, borrar datos de la base de datos, etc.) en la base de datos.
Para recuperar los datos de la base de datos, se debe crear una clase interna anónima para la interfase de consumidor.
Disposable disposable = appRepository.getAllTasks()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<Task>>() {
@Override
public void accept(List<Task> tasks) throws Exception {
onGetAllTaskSucess(tasks);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
//Se trata de funciones que deben ejecutarse cuando se llama la base de datos con éxito. También puede estar vacío o sólo emitir un mensaje en un "toast".
}
});
compositeDisposable.add(disposable);
En la interfaz "Consumer" se sobrescribe el método accept() (que se encuentra en una clase interna anónima). El método accept() obtiene como argumento (List<Task> tasks) la tabla "Task", que fue convertida de la base de datos en una lista.
En el método onGetAllTaskSucess(tareas) en la clase principal MainActivity.java todos los registros se cargan desde la base de datos y se almacenan en una lista.
private void onGetAllTaskSucess(List<Task> tasks) {
taskList.clear();
taskList.addAll(tasks);
adapter.notifyDataSetChanged();
}
Los datos se añaden a la base de datos (una nueva tarea - nuevo registro en la tabla de Task) también a través de un objeto desechable. Para editar los datos de la base de datos, se debe crear una clase interna anónima para la interfaz ObservableOnSubscribe.
addButton = (FloatingActionButton) findViewById(R.id.addTaskButon);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Disposable disposable = io.reactivex.Observable.create(new ObservableOnSubscribe<Object>(
) {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
//Comandos para guardar o borrar datos en la base de datos
Task task = new Task();
task.setTaskName(textfield1.getText());
appRepository.insertTask(task);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Consumer() {
@Override
public void accept(Object o) throws Exception {
//Imprimir mensaje correcto
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
//Muestra los mensajes de error de la variable lanzable ausgeben
}
}, new Action() {
@Override
public void run() throws Exception {
//Cargar datos de la base de datos de nuevo.
loadDatabaseData();
}
}
);
}
});
Cuando se hace clic en el botón añadir, el texto introducido por el usuario en el campo de texto "textfield1" se almacena en la base de datos en la columna "taskname". La interfaz "ObservableOnSubscribe" se utiliza para editar los datos de la base de datos. En esta interfaz, el único método "subscribe()" se sobrescribe con los comandos de la base de datos que se van a ejecutar. Finalmente se llama al método subscribe(), donde se programa lo que debería suceder después de editar los datos de la base de datos.
Los datos de la base de datos pueden ser procesados con estos comandos en una actividad (aquí: MainActivity.java). La mejor manera de mostrar los datos de la base de datos es un ListView, que entonces muestra todos los valores de un registro de la base de datos en una lista.