Room puede convertir tipos primitivos y encuadrados, pero no admite referencias de objetos entre entidades. Obtén información para usar conversores de tipo y por qué Room no admite referencias de objetos.
Cómo usar conversores de tipo
A veces, necesitas que la app almacene un tipo de datos personalizados en una sola columna de base de datos. Para admitir tipos personalizados, debes proporcionar conversores de tipo. Estas son funciones que indican a Room cómo convertir tipos personalizados en tipos conocidos y a partir de ellos que Room puede conservar. Para identificar los conversores de tipo, puedes usar la
@ColumnTypeConverter anotación.
Supongamos que necesitas conservar instancias de Date en
tu base de datos de Room. Room no puede conservar objetos Date de forma nativa, por lo que debes definir los conversores de tipo:
object Converters { @ColumnTypeConverter fun fromTimestamp(value: Long?): Date? { return value?.let { Date(it) } } @ColumnTypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time } }
En este ejemplo, se definen dos funciones de conversor de tipo: una que convierte un objeto Date en un objeto Long y otra que convierte un objeto Long en un objeto Date. Debido a que Room puede conservar objetos Long, puede usar estos conversores para conservar objetos Date.
A continuación, agrega la @ColumnTypeConverters
anotación a la clase AppDatabase para que Room pueda usar la clase de conversor
que definiste:
@Database(entities = [User::class], version = 1) @ColumnTypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao }
Con estos conversores de tipo definidos, puedes usar tu tipo personalizado en tus entidades y DAOs del mismo modo que usarías tipos primitivos:
@Entity data class User( @PrimaryKey val id: Long, val name: String, val birthday: Date? ) @Dao interface UserDao { @Query("SELECT * FROM user WHERE birthday = :targetDate") suspend fun findUsersBornOnDate(targetDate: Date): List<User> }
Debido a que agregaste la anotación @ColumnTypeConverters a AppDatabase en este ejemplo, Room puede usar el conversor de tipos definido en todas partes. Para determinar el alcance de los conversores de tipos en entidades o DAOs específicos, anota tus clases @Entity o @Dao con @ColumnTypeConverters.
Cómo controlar la inicialización de convertidores de tipo
Por lo general, Room crea instancias de conversores de tipo. Sin embargo, si necesitas pasar dependencias adicionales a las clases de conversores de tipo, tu app debe controlar directamente su inicialización. En ese caso, anota tu clase de conversor
con @ProvidedColumnTypeConverter:
@ProvidedColumnTypeConverter class ExampleConverter { @ColumnTypeConverter fun stringToExample(string: String?): ExampleType? { return string?.let { ExampleType() } } @ColumnTypeConverter fun exampleToString(example: ExampleType?): String? { return example?.toString() } }
Además de declarar tu clase de conversor en @ColumnTypeConverters, usa
la función RoomDatabase.Builder.addColumnTypeConverter para pasar una
instancia de tu clase de conversor al compilador RoomDatabase:
val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name") .addColumnTypeConverter(exampleConverterInstance) .build()
Por qué Room no permite referencias a objetos
Conclusión clave: Room no permite referencias de objetos entre clases de entidades. En su lugar, debes solicitar explícitamente los datos que necesita tu app.
La asignación de relaciones de una base de datos al modelo de objetos correspondiente es una práctica común y funciona muy bien en el servidor. Incluso cuando el programa carga propiedades a medida que se accede a ellas, el servidor sigue funcionando bien.
Sin embargo, en el cliente, este tipo de carga diferida no es factible porque, generalmente, ocurre en el subproceso de IU y la búsqueda de información en el subproceso de IU del disco crea problemas de rendimiento significativos. El subproceso de IU suele tener alrededor de 16 ms para calcular y dibujar el diseño actualizado de una actividad, por lo que, incluso si una búsqueda solo lleva 5 ms, es probable que tu app se quede sin tiempo para dibujar el marco de trabajo, lo que provocaría errores visuales notables. La búsqueda podría tardar más tiempo en completarse si hay una transacción independiente ejecutándose en paralelo o si el dispositivo ejecuta otras tareas que requieren mucha memoria. Sin embargo, si no usas la carga diferida, tu app obtiene más datos de los que necesita, lo que crea problemas de consumo de memoria.
Las asignaciones relacionales de objetos generalmente dejan esta decisión a los desarrolladores con el objetivo de que puedan hacer lo que sea mejor para los casos prácticos de sus apps. Por lo general, los desarrolladores deciden compartir el modelo entre su app y la IU. Sin embargo, esta solución no se ajusta bien porque, a medida que cambia la IU con el paso del tiempo, el modelo compartido crea problemas que son difíciles de anticipar y depurar para los desarrolladores.
Por ejemplo, considera una IU que carga una lista de objetos Book, cada uno con un objeto Author. Inicialmente, puedes diseñar tus búsquedas de modo que utilicen la carga diferida para que las instancias de Book obtenga el autor. La primera recuperación de la propiedad author consulta la base de datos. Un tiempo después, te das cuenta de que también necesitas mostrar el nombre del autor en la IU de tu app. Puedes acceder a este nombre, como se muestra en el siguiente fragmento de código:
Text(text = book.author.name)
Sin embargo, este cambio aparentemente insignificante hace que se busque la tabla Author en el subproceso principal.
Si consultas la información del autor con anticipación, pero no la necesitas, es difícil cambiar cómo se cargan esos datos. Por ejemplo, si la IU de tu app ya no necesita mostrar la información de Author, tu app carga datos de forma efectiva que ya no se muestran, lo que desperdicia espacio valioso en la memoria. La eficiencia de tu app se reduce aún más si la clase Author hace referencia a otra tabla, como Books.
Para hacer referencia simultáneamente a varias entidades con Room, crea un objeto de datos que contenga cada entidad y, luego, escribe una búsqueda que una las tablas correspondientes. Este modelo bien estructurado, combinado con las sólidas capacidades de validación de consultas de Room, permite que tu app consuma menos recursos durante la carga de datos, mejorando el rendimiento de tu app y la experiencia del usuario.