Icono del sitio Develou

Crear Una Base De Datos Room

En este tutorial vamos a crear una base de datos Room para una App Android de ejemplo sobre listas de compras.

El objetivo es crear los componentes vistos en la introducción a Room con el fin de mostrar las listas de compras en un TextView. E ilustrar el funcionamiento de la librería y sus componentes.

Puedes descargar el resultado final del tutorial desde el siguiente enlace:

Empecemos el desarrollo.


1. Crear Proyecto En Android Studio

  1. Abre Android Studio y crea un nuevo proyecto
  2. Selecciona una Empty Activity y presiona Next
  3. Nombra al aplicativo como Shopping List y presiona Finish.

2. Añadir Dependencias Gradle De Room

  1. Abre tu archivo build.gradle del módulo y agrega las siguiente dependencias.
dependencies {

    implementation "androidx.appcompat:appcompat:$appCompatVersion"

    // Room
    implementation "androidx.room:room-runtime:$roomVersion"
    annotationProcessor "androidx.room:room-compiler:$roomVersion"

    // Lifecycle
    implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycleVersion"
    implementation "androidx.lifecycle:lifecycle-livedata:$lifecycleVersion"
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"

    // UI
    implementation "com.google.android.material:material:$materialVersion"
    implementation "androidx.constraintlayout:constraintlayout:$contraintLayoutVersion"

    // Testing
    testImplementation "junit:junit:$junitVersion"
    androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
    androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
}

2. Ahora abre build.gradle del proyecto y crea un bloque llamado ext con el número de las versiones:

ext {
    appCompatVersion = '1.2.0'

    roomVersion = '2.2.5'
    lifecycleVersion = '2.2.0'

    materialVersion = '1.2.1'
    contraintLayoutVersion = '2.0.4'

    junitVersion = '4.13.1'
    espressoVersion = '3.3.0'
    androidxJunitVersion = '1.1.2'
}

Puedes obtener el número de versión más reciente desde AndroidX releases.


3. Crear Una Entidad

La primera característica que vamos a desarrollar es ver las listas de compras que existen.

En este momento la entidad tiene una clave primaria y el nombre como se muestra en el siguiente esquema:

¿Cómo crear una entidad?

  1. Añade la clase ShoppingList que represente a las listas de compra. Cada atributo hará referencia a la columna en la tabla.
public class ShoppingList {

    
    private final String mId;
    
    private final String mName;

    public ShoppingList(@NonNull String id, @NonNull String name) {
        mId = id;
        mName = name;
    }

    public String getId() {
        return mId;
    }

    public String getName() {
        return mName;
    }
}

2. Actualiza el modelo con las siguientes anotaciones:

@Entity(tableName = "shopping_list")
public class ShoppingList {

    @PrimaryKey
    @NonNull
    @ColumnInfo(name = "id")
    private final String mId;

    @NonNull
    @ColumnInfo(name = "name")
    private final String mName;

    public ShoppingList(@NonNull String id, @NonNull String name) {
        mId = id;
        mName = name;
    }

    public String getId() {
        return mId;
    }

    public String getName() {
        return mName;
    }
}

¿Cuál es el propósito de cada una?


4. Crear DAO

Un DAO (Data Access Object) u objeto de acceso a datos es la clase principal donde se definen las interacciones con tu base de datos.

Aunque podemos tener uno solo para todos los accesos, es recomendado crear uno por cada tabla operada.

Ahora bien, ¿qué acciones realizaremos en la tabla shopping_list?

Por el momento:

Vamos a plasmar esto creando una interfaz (también puede ser una clase abstracta) con el nombre de ShoppingListDao con el siguiente código:

@Dao
public interface ShoppingListDao {
    @Query("SELECT * FROM shopping_list")
    LiveData<List<ShoppingList>> getAll();

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    void insert(ShoppingList shoppingList);
}

Analicemos las partes:

Como ves, getAll() retorna un LiveData. Esto se debe a que Room se las arregla para interactuar con este componente y enviarnos actualizaciones sobre cambios en el esquema de la tabla shopping_list.

En los siguientes tutoriales iremos expandiendo este DAO según lo requiera la incorporación de nuevas funcionalidades.


5. Crear Base De Datos Room

Habíamos dicho en la introducción que RoomDatabase es la clase base para todas las bases de datos en Room. Así que debes extender tu clase de base de datos de ella.

Esta provee métodos de acceso directo hacia las operaciones de la base de datos Room, sin embargo deberíamos preferir usar los DAOs para ello.

Crea una nueva clase abstracta llamada ShoppingListDatabase y agrega el siguiente código:

@Database(entities = {ShoppingList.class}, version = 1, exportSchema = false)
public abstract class ShoppingListDatabase extends RoomDatabase {

    // Exposición de DAOs
    public abstract ShoppingListDao shoppingListDao();

    private static final String DATABASE_NAME = "shopping-list-db";

    private static ShoppingListDatabase INSTANCE;

    private static final int THREADS = 4;

    public static final ExecutorService dbExecutor = Executors.newFixedThreadPool(THREADS);

    public static ShoppingListDatabase getInstance(final Context context) {
        if (INSTANCE == null) {
            synchronized (ShoppingListDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(
                            context.getApplicationContext(), ShoppingListDatabase.class,
                            DATABASE_NAME)
                            .build();
                }
            }
        }
        return INSTANCE;
    }

}

Resaltemos las características:


6. Crear Repositorio

El siguiente paso es crear la clase del repositorio de las listas de compras.

¿Qué debemos tener en cuenta?

Crea la clase ShoppingListRepository y pega el siguiente código:

public class ShoppingListRepository {
    private final LiveData<List<ShoppingList>> mShoppingLists;
    private final ShoppingListDao mShoppingListDao;

    public ShoppingListRepository(Context context) {
        ShoppingListDatabase db = ShoppingListDatabase.getInstance(context);
        mShoppingListDao = db.shoppingListDao();
        mShoppingLists = mShoppingListDao.getAll();
    }

    public LiveData<List<ShoppingList>> getAllShoppingLists() {
        return mShoppingLists;
    }

    public void insert(ShoppingList shoppingList) {
        ShoppingListDatabase.dbExecutor.execute(
                () -> mShoppingListDao.insert(shoppingList)
        );
    }
}

En el constructor ordenaremos una carga automática de las listas de compra con el fin de recibir las notificaciones de datos lo más pronto posible.

De esta forma, Room envía a otro hilo de trabajo a las consultas con LiveData y se asegura de comunicar los cambios a la UI.

En el caso de la inserción debemos llamar al ExecutorService para ejecutarla en su hilo correspondiente.


7. Crear ViewModel

Para crear el ViewModel nospreguntamos:

¿Cuáles son las interacciones que vendrán desde la interfaz?

En este momento solo vamos a insertar un par de registros programaticamente y los mostraremos en el TextView que aparece en el layout creado automáticamente por la plantilla Empty Activity.

Por lo que observaremos la lista de listas de compras con un LiveData desde MainActivity.

Esto significa que envolveremos a los métodos getAllShoppingLists() e insert() del repositorio en los del ViewModel.

Teniendo en mente lo anterior, crea la clase ShoppingListViewModel y materializa estas características:

public class ShoppingListViewModel extends AndroidViewModel {

    private final ShoppingListRepository mRepository;

    private final LiveData<List<ShoppingList>> mShoppingLists;

    public ShoppingListViewModel(@NonNull Application application) {
        super(application);
        mRepository = new ShoppingListRepository(application);
        mShoppingLists = mRepository.getAllShoppingLists();
    }

    public LiveData<List<ShoppingList>> getShoppingLists() {
        return mShoppingLists;
    }

    public void insert(ShoppingList shoppingList) {
        mRepository.insert(shoppingList);
    }

}

En el constructor cargaremos todas las listas de compra para actualizar la vista inmediatamente.


8. Prepoblar La Base De Datos

Si queremos que la base de datos comience con registros predefinidos podemos hacer uso de RoomDatabase#Callback.

Crea una callback en la clase de base de datos y sobrescribe su método onCreate() para agrega 2 listas de compras:

// Prepoblar base de datos con callback
    private static final RoomDatabase.Callback mRoomCallback = new Callback() {
        @Override
        public void onCreate(@NonNull SupportSQLiteDatabase db) {
            super.onCreate(db);

            dbExecutor.execute(() -> {
                ShoppingListDao dao = INSTANCE.shoppingListDao();

                ShoppingList list1 = new ShoppingList("1", "Lista de ejemplo");
                ShoppingList list2 = new ShoppingList("2", "Banquete de Navidad");

                dao.insert(list1);
                dao.insert(list2);
            });
        }
    };

Luego la añádela con addCallback() en el builder.

INSTANCE = Room.databaseBuilder(
                            context.getApplicationContext(), ShoppingListDatabase.class,
                            DATABASE_NAME)
                            .addCallback(mRoomCallback)
                            .build();

9. Mostrar Datos En La Actividad

La actividad creada por defecto trae consigo un layout con un TextView. Algo así:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/db_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Finalizando, conecta ese view con los datos de la tabla.

Es decir, Obtenemos el ViewModel en el método onCreate() y observamos su contenido con observe():

public class MainActivity extends AppCompatActivity {


    private ShoppingListViewModel mViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView dbText = findViewById(R.id.db_text);

        ViewModelProvider.AndroidViewModelFactory factory =
                ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication());

        mViewModel = new ViewModelProvider(this, factory)
                .get(ShoppingListViewModel.class);

        mViewModel.getShoppingLists().observe(this, shoppingLists -> {
                    StringBuilder sb = new StringBuilder();
                    for (ShoppingList list : shoppingLists) {
                        sb.append(list.getName()).append("\n");
                    }
                    dbText.setText(sb.toString());
                }
        );

    }
}

Al usar un StringBuilder podemos concatenar el nombre de cada lista y proyectarlo en el texto con setText().

Y para terminar, ejecuta el proyecto.

Deberías ver lo siguiente:


Ver siguiente tutorial: Insertar Datos Con Room

Salir de la versión móvil