Fare riferimento a dati complessi utilizzando Room

La stanza virtuale offre funzionalità per la conversione da tipi primitivi a formati confezionati ma non consentono riferimenti agli oggetti tra entità. Questo documento spiega come utilizzare i convertitori dei tipi e perché Room non supporta gli oggetti riferimenti.

Utilizzare convertitori dei tipi

A volte, hai bisogno che l'app archivi un tipo di dati personalizzato in un unico database colonna. Supporti i tipi personalizzati fornendo i convertitori dei tipi, che sono che indicano a Room come convertire i tipi personalizzati da e verso tipi noti che La stanza può persistere. Per identificare gli utenti che hanno completato una conversione dei tipi utilizzando @TypeConverter.

Supponi di dover mantenere le istanze di Date in il database della stanza. La stanza virtuale non sa come rendere persistenti Date oggetti, quindi devi per definire i convertitori dei tipi:

Kotlin

class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? {
    return value?.let { Date(it) }
  }

  @TypeConverter
  fun dateToTimestamp(date: Date?): Long? {
    return date?.time?.toLong()
  }
}

Java

public class Converters {
  @TypeConverter
  public static Date fromTimestamp(Long value) {
    return value == null ? null : new Date(value);
  }

  @TypeConverter
  public static Long dateToTimestamp(Date date) {
    return date == null ? null : date.getTime();
  }
}

Questo esempio definisce due metodi del convertitore dei tipi: uno che converte un Date in un oggetto Long e uno che esegue la conversione inversa Da Long a Date. Poiché la stanza virtuale sa come rendere persistenti Long oggetti, può utilizzare di questi convertitori in modo da rendere persistenti Date oggetti.

In seguito, aggiungi la classe @TypeConverters alla classe AppDatabase in modo che la stanza sappia dell'utente che ha completato una conversione che hai definito:

Kotlin

@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
  abstract fun userDao(): UserDao
}

Java

@Database(entities = {User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
  public abstract UserDao userDao();
}

Una volta definiti questi utenti che hanno completato una conversione, puoi utilizzare il tuo tipo personalizzato nel entità e DAO nello stesso modo in cui utilizzeresti i tipi primitivi:

Kotlin

@Entity
data class User(private val birthday: Date?)

@Dao
interface UserDao {
  @Query("SELECT * FROM user WHERE birthday = :targetDate")
  fun findUsersBornOnDate(targetDate: Date): List<User>
}

Java

@Entity
public class User {
  private Date birthday;
}

@Dao
public interface UserDao {
  @Query("SELECT * FROM user WHERE birthday = :targetDate")
  List<User> findUsersBornOnDate(Date targetDate);
}

In questo esempio, Room può utilizzare ovunque il convertitore di tipi definito perché annotato AppDatabase con @TypeConverters. Tuttavia, puoi anche digitare l'ambito convertitori in entità o DAO specifiche annotando @Entity o @Dao corsi con @TypeConverters.

Inizializzazione del convertitore dei tipi di controllo

Di solito, la stanza virtuale gestisce l'istanza dei convertitori dei tipi per te. Tuttavia, a volte potresti dover passare ulteriori dipendenze al convertitore di tipi il che significa che la tua app deve controllare direttamente l'inizializzazione dei tuoi convertitori di tipo. In questo caso, annota la classe dell'utente che ha completato una conversione con @ProvidedTypeConverter:

Kotlin

@ProvidedTypeConverter
class ExampleConverter {
  @TypeConverter
  fun StringToExample(string: String?): ExampleType? {
    ...
  }

  @TypeConverter
  fun ExampleToString(example: ExampleType?): String? {
    ...
  }
}

Java

@ProvidedTypeConverter
public class ExampleConverter {
  @TypeConverter
  public Example StringToExample(String string) {
    ...
  }

  @TypeConverter
  public String ExampleToString(Example example) {
    ...
  }
}

Poi, oltre a dichiarare la classe dell'utente che ha completato una conversione in @TypeConverters, utilizza il RoomDatabase.Builder.addTypeConverter() per passare un'istanza della classe del convertitore a RoomDatabase Builder:

Kotlin

val db = Room.databaseBuilder(...)
  .addTypeConverter(exampleConverterInstance)
  .build()

Java

AppDatabase db = Room.databaseBuilder(...)
  .addTypeConverter(exampleConverterInstance)
  .build();

Comprendere perché la stanza virtuale non consente riferimenti agli oggetti

Concetto chiave: La stanza virtuale non consente i riferimenti agli oggetti tra classi di entità. Devi invece richiedere esplicitamente i dati necessari alla tua app.

La mappatura delle relazioni da un database al rispettivo modello a oggetti è un'attività comune e funziona molto bene sul lato server. Anche quando il programma viene caricato campi al loro accesso, il server funziona comunque bene.

Tuttavia, sul lato client, questo tipo di caricamento lento non è fattibile perché di solito avviene nel thread dell'interfaccia utente e l'esecuzione di query sulle informazioni su disco nell'interfaccia utente del thread crea problemi di prestazioni significativi. Il thread dell'interfaccia utente in genere ha 16 ms per calcolare e tracciare il layout aggiornato di un'attività, in modo che anche se richiede solo 5 ms, è comunque probabile che per la tua app si esaurisca il tempo disegna l'inquadratura, causando evidenti problemi visivi. La query potrebbe richiedere anche tempo per il completamento se è in corso una transazione separata in parallelo oppure se il dispositivo esegue altre attività che usano molto disco. Se non utilizzi la funzionalità Lazy mentre viene caricato, tuttavia, l'app recupera più dati del necessario, creando memoria problemi di consumo.

Di solito le mappature relazionali degli oggetti lasciano questa decisione agli sviluppatori, possono fare tutto ciò che è meglio per i casi d'uso delle loro app. Gli sviluppatori di solito decidono di condividere il modello tra l'app e la UI. Questa soluzione non scalare bene, poiché man mano che la UI cambia nel tempo, il modello condiviso crea problemi difficili da prevedere ed eseguire il debug per gli sviluppatori.

Ad esempio, considera una UI che carica un elenco di Book oggetti, con ogni libro che dispone di un oggetto Author. Inizialmente, potresti progettare le query in modo da utilizzare la funzione Caricamento in corso per fare in modo che le istanze di Book recuperino l'autore. Il primo recupero Il campo author esegue una query sul database. Qualche tempo dopo, ti rendi conto che devono visualizzare il nome dell'autore nell'interfaccia utente dell'app. Puoi accedere a questa di nome con facilità, come mostrato nello snippet di codice riportato di seguito:

Kotlin

authorNameTextView.text = book.author.name

Java

authorNameTextView.setText(book.getAuthor().getName());

Tuttavia, questa modifica apparentemente innocua provoca l'esecuzione di query sulla tabella Author sul thread principale.

Se esegui query anticipatamente sulle informazioni sull'autore, diventa difficile modificare la modalità di caricamento dei dati se non ne hai più bisogno. Ad esempio, se la tua app La UI non deve più mostrare le informazioni di Author, l'app viene caricata in modo efficace che non visualizza più, sprecando spazio in memoria preziosa. Della tua app l'efficienza peggiora ulteriormente se la classe Author fa riferimento a un'altra tabella, ad esempio Books.

Per fare riferimento a più entità contemporaneamente utilizzando una stanza virtuale, devi creare una POJO che contenga ogni entità, quindi scrivi una query che unisca tabelle. Questo modello ben strutturato, combinato con l'efficace query di ricerca Room di convalida, consente alla tua app di consumare meno risorse durante il caricamento migliorando le prestazioni e l'esperienza utente della tua app.