# Bases de datos locales con Room
Vamos a hacer una aplicación que guarde el tiempo usado en hacer actividades en una base de datos local:


## Dependencias
Vamos a usar las siguientes dependencias, igual habrá que actualizarlas en el momento que hagamos el proyecto...ya veremos!
En el build.gradle (module:app) vamos a pegar las siguientes dependencias:
```
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("kapt")
id ("com.google.dagger.hilt.android")
}
android {
namespace = "com.anluisa.roomcronoapp"
compileSdk = 33
defaultConfig {
applicationId = "com.anluisa.roomcronoapp"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.3.2"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
// Room
val room_version = "2.5.1"
implementation ("androidx.room:room-ktx:$room_version")
kapt ("androidx.room:room-compiler:$room_version")
// Dagger Hilt
implementation ("com.google.dagger:hilt-android:2.46.1")
kapt ("com.google.dagger:hilt-compiler:2.46.1")
//Navigation
val nav_version = "2.5.3"
implementation("androidx.navigation:navigation-compose:$nav_version")
// Swipe
implementation ("me.saket.swipe:swipe:1.1.1")
implementation("androidx.core:core-ktx:1.8.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
implementation("androidx.activity:activity-compose:1.5.1")
implementation(platform("androidx.compose:compose-bom:2022.10.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2022.10.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}
```
En el build.gradle (Project) debe quedar así:
```
plugins {
id("com.android.application") version "8.1.4" apply false
id ("com.android.library") version "8.1.4" apply false
id("org.jetbrains.kotlin.android") version "1.7.20" apply false
id ("com.google.dagger.hilt.android") version "2.46.1" apply false
}
```
## Modelo entidad de la base de datos
Vamos ahora a comenzar por diseñar el Modelo-Entidad de nuestra base de datos.
Creamos un package model y dentro un DataClass Cronos.
En bases de datos móviles lo normal es trabajar con Sqlite que es básicamente igual que cualquier otra base de datos en la que estén trabajando, pero en este caso usaremos el framework Room.
Leamos primero algo de Room, aunque como siempre se entenderá mejor en la práctica:
https://developer.android.com/training/data-storage/room?hl=es-419#kts
Tienen un instructivo para practicar aquí:
https://developer.android.com/codelabs/android-room-with-a-view-kotlin?hl=es-419#0
Como sé que están a tope con bases de datos no voy a dar mucha más explicación:
```
@Entity(tableName="cronos")
data class Cronos(
@PrimaryKey(autoGenerate = true)
val id: Long=0,
@ColumnInfo(name = "title")
val title: String,
@ColumnInfo(name="crono")
val crono: Long
)
```
Como ven declaramos cada campo con un par @ColumnInfo(name=) y definición de tipos, separamos cada definición de columna con una coma. Creo que viéndolo hecho se ve sencillo.
Cada tabla de nuestra base de datos deberá tener su propio modelo.
## Dao y creación de consultas
Creamos un nuevo package de nombre Room, tendrá dos archivos principalmente, el de configuración y uno con el manejo de la base de datos.
Vamos a crear primero una interface, CronosDataBaseDao.
Por seguir una arquitectura limpia la estructura que seguiremos será
Interface -> Repositorio ->ViewModel->View
Es decir creamos una interface y un repositorio de métodos, que usará el viewModel para finalmente mostrar los datos en las Vistas.
Usaremos varias cosas que ya hemos visto como que lo que los datos que queremos que se muestren (respuesta a una Query), los pasamos al Flow, pero como ves se hace un crud en un momento, con las operaciones ya predefinidas y el nombre de cada método que queremos usar:
```
// Interface vamos a crear los métodos,crearemos un repositorio que usará el viewModel que será el que manejará las vistas
//Esta interface será un DAO, Data Access Observer
@Dao
interface CronosDatabaseDao {
//Crud
@Query("SELECT * FROM cronos")
fun getCronos(): Flow<List<Cronos>>
@Query("SELECT * FROM cronos WHERE id=:id")
fun getCronosById(id:Long): Flow<Cronos>
@Insert(onConflict=OnConflictStrategy.REPLACE)
suspend fun insert(cronos:Cronos)
@Update(onConflict=OnConflictStrategy.REPLACE)
suspend fun update(cronos:Cronos)
@Delete
suspend fun delete (crono:Cronos)
}
```
El otro archivo que tenemos que generar es el de configuración de la base de datos, crearemos un nuevo archivo CronosDataBase como una clase, pero una vez será una clase abstracta, no será instanciada pero sus métodos sí se herederarán.
```
@Database(entities = [Cronos::class], version=1, exportSchema=false)
abstract class CronosDataBase: RoomDatabase() {
abstract fun cronosDao(): CronosDatabaseDao
}
```
## Inyección de dependencias
La **inyección de dependencias** es un patrón de diseño que nos permite suministrar las instancias necesarias en cada una de las clases que lo requieran. Imagina que tienes una clase llamada "Concierto". Dentro de esta clase, necesitas instancias de un pianista, un piano y el público. La inyección de dependencias se encarga de proporcionar todas esas instancias al concierto sin que nuestra clase tenga que preocuparse por crearlas.
## Ventajas de la Inyección de Dependencias
1. **Facilita la gestión de dependencias**: No tienes que preocuparte por crear manualmente todas las instancias que necesitas.
2. **Código más limpio y mantenible**: Al separar la creación de objetos, tu código se vuelve más organizado y fácil de mantener.
3. **Esencial para proyectos grandes y pruebas**: En desarrollos extensos y en pruebas, la inyección de dependencias es fundamental.
## Bibliotecas en Kotlin
En Kotlin, existen varias bibliotecas para implementar la inyección de dependencias:
1. **Dagger Hilt**: Simplifica la inyección de dependencias en proyectos Android desarrollados en Kotlin. Es parte de las integraciones de Android Jetpack y es recomendado por Google.
2. **Koin**: Es un marco pragmático de inyección de dependencias ligero para desarrolladores de Kotlin.
En resumen, la inyección de dependencias es como preparar los objetos detrás de escena para que cada clase reciba lo que necesita sin tener que instanciarlo directamente. ¡Es una herramienta poderosa para escribir aplicaciones más eficientes y mantenibles! 🚀.
Pequeño video explicativo de nuestro canario favorito:
https://www.youtube.com/watch?v=t6ZuzSu2UHI&ab_channel=Programaci%C3%B3nAndroidbyAristiDevs
## Configurar DaggerHilt
Para poder usarlo correctamente necesitamos tener una de las librerías que gestionan la inyección de dependencias.
Vamos a usar DaggerHilt
Vayamos como siempre a la docuemntación, y como siempre, más sencillo empezar con una práctica:
https://developer.android.com/training/dependency-injection?hl=es-419
Creamos una clase en la raíz del proyecto: CronosApplication, por ahora nuestra estructura va quedando así:

Aquí por ahora simplemente vamos a anunciar el uso del Hilt en nuestra aplicación:
```
@HiltAndroidApp
class CronosApplication: Application() {
}
```
Tenemos que ir al manifest para también incluirlo en la parte del Applicatoin
`android:name=".CronosApplication"`
Una vez hecho esto creamos un package "di" (dependency injection), y dentro un archivo con nombre AppModule tipo object que es donde vamos a estar definiendo las clases inyectadas.
```
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Singleton
@Provides
fun providesCronosDao(cronosDatabase:CronosDataBase): CronosDatabaseDao{
return cronosDatabase.cronosDao()
}
@Singleton
@Provides
fun providesCronosDatabase(@ApplicationContext context: Context): CronosDataBase{
return Room.databaseBuilder(
context,
CronosDataBase:: class.java, "cronos_db"
).fallbackToDestructiveMigration()
.build()
}
}
```
El año pasado en ETS les nombré esta página y la usamos para ver codeSmells:
https://refactoring.guru/es/design-patterns
Pero también está especializada en design-patterns, por qué hacemos todo este follón? Porque crear una instancia de la base de datos para cada acceso u operación, cargaría nuestro móvil de objetos en memoria, cuando realmente sólo necesitas un controlador para acceder a un único recurso. Por ejemplo el viewModel es un patrón parecido al singleton (no es exactamente igual pero sirve de ejemplo), tenemos varios proceso que quieren acceder a la IU y a través del ViewModel centralizamos el acceso, mejorando el rendimiento.
Con la base de datos es igual, pueden haber varios recursos queriendo acceder a la bd, pero sólo queremos una instancia que atienda a todos!
Lee algo sobre singleton y otros patrones en la web y como la cosa se lía y para que puedan procesar todo. Hagan el taller específico de Room:
https://developer.android.com/codelabs/android-room-with-a-view-kotlin?hl=es-419#0
También tenemos un curso bastante compacto pero por desgracia obsoleto sobre todo esto aplicado a Kotlin en:
https://kotlin.desarrollador-android.com/category/patrones-de-diseno/patrones-creacionales/
O sigan con el curso afianzando conceptos más básicos...ya ven que a partir de que la aplicación crece un poco y usa más recursos la cosa se lía.