Room può convertire tipi primitivi e boxed, ma non consente riferimenti a oggetti tra le entità. Scopri come utilizzare i convertitori di tipi e perché Room non supporta i riferimenti a oggetti.
Utilizzare i convertitori di tipi
A volte, devi fare in modo che la tua app memorizzi un tipo di dati personalizzato in una singola colonna del database. Puoi supportare i tipi personalizzati fornendo convertitori di tipi. Si tratta di funzioni che indicano a Room come convertire i tipi personalizzati in tipi noti che Room può rendere persistenti e viceversa. Per identificare i convertitori di tipi, utilizza l'
@ColumnTypeConverter annotazione.
Supponiamo che tu debba rendere persistenti le istanze di Date in
nel database Room. Room non può rendere persistenti in modo nativo gli oggetti Date, quindi devi definire i convertitori di tipi:
object Converters { @ColumnTypeConverter fun fromTimestamp(value: Long?): Date? { return value?.let { Date(it) } } @ColumnTypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time } }
Questo esempio definisce due funzioni di conversione dei tipi: una che converte un oggetto Date in un oggetto Long e una che converte un oggetto Long in un oggetto Date. Poiché Room può rendere persistenti gli oggetti Long, può utilizzare questi convertitori per rendere persistenti gli oggetti Date.
Poi, aggiungi l'annotazione @ColumnTypeConverters
alla classe AppDatabase in modo che Room possa utilizzare la classe di conversione
che hai definito:
@Database(entities = [User::class], version = 1) @ColumnTypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao }
Con questi convertitori di tipi definiti, puoi utilizzare il tipo personalizzato nelle entità e nei DAO come faresti con i tipi primitivi:
@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> }
Poiché in questo esempio hai annotato AppDatabase con @ColumnTypeConverters, Room può utilizzare il convertitore di tipi definito ovunque. Per limitare l'ambito dei convertitori di tipi a entità o DAO specifici, annota le classi @Entity o @Dao con @ColumnTypeConverters.
Controllare l'inizializzazione del convertitore di tipi
In genere, Room crea un'istanza dei convertitori di tipi per te. Tuttavia, se devi passare dipendenze aggiuntive alle classi di conversione dei tipi, la tua app deve controllare direttamente la loro inizializzazione. In questo caso, annota la classe di conversione con
@ProvidedColumnTypeConverter:
@ProvidedColumnTypeConverter class ExampleConverter { @ColumnTypeConverter fun stringToExample(string: String?): ExampleType? { return string?.let { ExampleType() } } @ColumnTypeConverter fun exampleToString(example: ExampleType?): String? { return example?.toString() } }
Oltre a dichiarare la classe di conversione in @ColumnTypeConverters, utilizza
la RoomDatabase.Builder.addColumnTypeConverter funzione per passare un'
istanza della classe di conversione al builder RoomDatabase:
val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name") .addColumnTypeConverter(exampleConverterInstance) .build()
Comprendere perché Room non consente i riferimenti a oggetti
Informazione chiave: Room non consente i riferimenti a oggetti tra le classi di entità. Devi invece richiedere esplicitamente i dati di cui ha bisogno la tua app.
Il mapping delle relazioni da un database al rispettivo modello di oggetti è una pratica comune e funziona molto bene sul lato server. Anche quando il programma carica le proprietà man mano che vengono utilizzate, il server continua a funzionare bene.
Tuttavia, sul lato client, questo tipo di caricamento lento non è fattibile perché in genere si verifica nel thread dell'interfaccia utente e l'esecuzione di query sulle informazioni sul disco nel thread dell'interfaccia utente crea problemi di prestazioni significativi. In genere, il thread dell'interfaccia utente ha circa 16 ms per calcolare e disegnare il layout aggiornato di un'attività, quindi anche se una query richiede solo 5 ms, è comunque probabile che la tua app non abbia il tempo di disegnare il frame, causando evidenti problemi visivi. La query potrebbe richiedere ancora più tempo per essere completata se è in esecuzione in parallelo una transazione separata o se il dispositivo sta eseguendo altre attività che richiedono un uso intensivo del disco. Se non utilizzi il caricamento lazy, tuttavia, la tua app recupera più dati di quelli necessari, creando problemi di consumo di memoria.
In genere, i mapping relazionali a oggetti lasciano questa decisione agli sviluppatori in modo che possano fare ciò che è meglio per i casi d'uso della loro app. In genere, gli sviluppatori decidono di condividere il modello tra l'app e l'UI. Tuttavia, questa soluzione non è scalabile perché, man mano che l'UI cambia nel tempo, il modello condiviso crea problemi che gli sviluppatori hanno difficoltà ad anticipare e di cui è difficile eseguire il debug.
Ad esempio, considera un'UI che carica un elenco di oggetti Book, ognuno dei quali ha un oggetto Author. Inizialmente potresti progettare le query in modo da utilizzare il caricamento lazy per fare in modo che le istanze di Book recuperino l'autore. Il primo recupero della proprietà author esegue una query sul database. Qualche tempo dopo, ti rendi conto che devi visualizzare anche il nome dell'autore nell'UI della tua app. Puoi accedere a questo nome, come mostrato nel seguente snippet di codice:
Text(text = book.author.name)
Tuttavia, questa modifica apparentemente innocua fa sì che la tabella Author venga sottoposta a query nel thread principale.
Se esegui una query sulle informazioni sull'autore in anticipo, ma non ne hai bisogno, è difficile modificare la modalità di caricamento dei dati. Ad esempio, se l'UI della tua app non ha più bisogno di visualizzare le informazioni Author, la tua app carica effettivamente i dati che non visualizza, sprecando spazio di memoria prezioso. L'efficienza della tua app peggiora ulteriormente se la classe Author fa riferimento a un'altra tabella, ad esempio Books.
Per fare riferimento a più entità contemporaneamente utilizzando Room, crea un oggetto dati che contenga ogni entità, quindi scrivi una query che esegua il join delle tabelle corrispondenti. Questo modello ben strutturato, combinato con le solide funzionalità di convalida delle query di Room, consente alla tua app di consumare meno risorse durante il caricamento dei dati, migliorando le prestazioni e l'esperienza utente dell'app.