Android-Architekturkomponenten: die Room-Persistenzbibliothek
German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)
In diesem letzten Artikel der Serie über Android-Architekturkomponenten untersuchen wir die Room-Persistenzbibliothek, eine hervorragende neue Ressource, die die Arbeit mit Datenbanken in Android erheblich erleichtert. Es bietet eine Abstraktionsschicht über SQLite, zur Kompilierzeit geprüfte SQL-Abfragen und auch asynchrone und beobachtbare Abfragen. Room bringt Datenbankoperationen auf Android auf eine andere Ebene.
Da dies der vierte Teil der Reihe ist, gehe ich davon aus, dass Sie mit den Konzepten und Komponenten des Architekturpakets wie LiveData und LiveModel vertraut sind. Wenn Sie jedoch keinen der letzten drei Artikel gelesen haben, können Sie trotzdem folgen. Wenn Sie jedoch nicht viel über diese Komponenten wissen, nehmen Sie sich etwas Zeit, um die Serie zu lesen – Sie können es genießen.
1. Die Room-Komponente
Wie bereits erwähnt, ist Room kein neues Datenbanksystem. Es ist eine abstrakte Schicht, die die von Android übernommene Standard-SQLite-Datenbank umschließt. Room fügt SQLite jedoch so viele Funktionen hinzu, dass es fast unmöglich ist, sie zu erkennen. Room vereinfacht alle datenbankbezogenen Operationen und macht sie auch viel leistungsfähiger, da es die Möglichkeit bietet, Observables und zur Kompilierzeit geprüfte SQL-Abfragen zurückzugeben.
Room besteht aus drei Hauptkomponenten: der Datenbank, dem DAO (Data Access Objects) und der Entität. Jede Komponente hat ihre Verantwortung und alle müssen implementiert werden, damit das System funktioniert. Glücklicherweise ist eine solche Implementierung recht einfach. Dank der bereitgestellten Annotationen und abstrakten Klassen wird der Boilerplate zur Implementierung von Room auf ein Minimum reduziert.
- Entity ist die Klasse, die in der Datenbank gespeichert wird. Für jede mit
@Entity
annotierte Klasse wird eine exklusive Datenbanktabelle erstellt. - Das DAO ist die mit
@Dao
annotierte Schnittstelle, die den Zugriff auf Objekte in der Datenbank und deren Tabellen vermittelt. Es gibt vier spezifische Anmerkungen für die grundlegenden DAO-Operationen:@Insert
,@Update
,@Delete
und@Query
. - Die Database-Komponente ist eine mit
@Database
annotierte abstrakte Klasse, dieRoomDatabase
erweitert. Die Klasse definiert die Liste der Entitäten und ihre DAOs.
2. Einrichten der Umgebung
Um Room zu verwenden, fügen Sie dem App-Modul in Gradle die folgenden Abhängigkeiten hinzu:
compile "android.arch.persistence.room:runtime:1.0.0" annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
Wenn Sie Kotlin verwenden, müssen Sie das kapt
-Plugin anwenden und eine weitere Abhängigkeit hinzufügen.
apply plugin: 'kotlin-kapt' // … dependencies { // … kapt "android.arch.persistence.room:compiler:1.0.0" }
3. Entität, die Datenbanktabelle
Eine Entität stellt das Objekt dar, das in der Datenbank gespeichert wird. Jede Entity
-Klasse erstellt eine neue Datenbanktabelle, wobei jedes Feld eine Spalte darstellt. Anmerkungen werden zum Konfigurieren von Entitäten verwendet, und ihr Erstellungsprozess ist wirklich einfach. Beachten Sie, wie einfach es ist, eine Entity
mit Kotlin-Datenklassen einzurichten.
@Entity data class Note( @PrimaryKey( autoGenerate = true ) var id: Long?, var text: String?, var date: Long? )
Sobald eine Klasse mit @Entity
annotiert ist, erstellt die Room-Bibliothek automatisch eine Tabelle mit den Klassenfeldern als Spalten. Wenn Sie ein Feld ignorieren müssen, kommentieren Sie es einfach mit @Ignore
. Jede Entity
muss außerdem einen @PrimaryKey
definieren.
Tabelle und Spalten
Room verwendet die Klasse und ihre Feldnamen, um automatisch eine Tabelle zu erstellen. Sie können jedoch die generierte Tabelle personalisieren. Um einen Namen für die Tabelle zu definieren, verwenden Sie die Option tableName
in der Annotation @Entity
. Fügen Sie zum Bearbeiten des Spaltennamens eine Annotation @ColumnInfo
mit der Option name in das Feld ein. Beachten Sie, dass bei den Tabellen- und Spaltennamen die Groß-/Kleinschreibung beachtet werden muss.
@Entity( tableName = “tb_notes” ) data class Note( @PrimaryKey( autoGenerate = true ) @ColumnInfo( name = “_id” ) var id: Long?, //... )
Indizes und Eindeutigkeitsbeschränkungen
Es gibt einige nützliche SQLite-Einschränkungen, die Room uns ermöglicht, auf unseren Entitäten leicht zu implementieren. Um die Suchabfragen zu beschleunigen, können Sie SQLite-Indizes in den Feldern erstellen, die für solche Abfragen relevanter sind. Indizes machen Suchanfragen viel schneller; Sie verlangsamen jedoch auch das Einfügen, Löschen und Aktualisieren von Abfragen, sodass Sie sie sorgfältig verwenden müssen. Sehen Sie sich die SQLite-Dokumentation an, um sie besser zu verstehen.
Es gibt zwei verschiedene Möglichkeiten, Indizes in Room zu erstellen. Sie können einfach die ColumnInfo
-Eigenschaft index
auf true
setzen, damit Room die Indizes für Sie festlegen kann.
@ColumnInfo(name = "date", index = true) var date: Long
Wenn Sie mehr Kontrolle benötigen, verwenden Sie die indices
-Eigenschaft der @Entity
-Annotation und listen Sie die Namen der Felder auf, die den Index in der value
-Eigenschaft bilden müssen. Beachten Sie, dass die Reihenfolge der Elemente im value
wichtig ist, da sie die Sortierung der Indextabelle definiert.
@Entity( tableName = "tb_notes", indices = arrayOf( Index( value = *arrayOf("date","title"), name = "idx_date_title" ) ) )
Eine weitere nützliche SQLite-Einschränkung ist unique
, die verbietet, dass das markierte Feld doppelte Werte enthält. Leider stellt Room in Version 1.0.0 diese Eigenschaft nicht direkt im Entitätsfeld bereit. Sie können jedoch einen Index erstellen und ihn eindeutig machen, um ein ähnliches Ergebnis zu erzielen.
@Entity( tableName = "tb_users", indices = arrayOf( Index( value = “username”, name = "idx_username", unique = true ) ) )
Andere Einschränkungen wie NOT NULL
, DEFAULT
und CHECK
sind in Room nicht vorhanden (zumindest bis jetzt in Version 1.0.0), aber Sie können Ihre eigene Logik für die Entity erstellen, um ähnliche Ergebnisse zu erzielen. Um Nullwerte für Kotlin-Entitäten zu vermeiden, entfernen Sie einfach das ?
am Ende des Variablentyps oder fügen Sie in Java die Annotation @NonNull
hinzu.
Beziehung zwischen Objekten
Im Gegensatz zu den meisten objektrelationalen Mapping-Bibliotheken erlaubt Room nicht, dass eine Entität direkt auf eine andere verweist. Dies bedeutet, dass Sie, wenn Sie eine Entität namens NotePad
und eine namens Note
haben, keine Collection
von Note
innerhalb des NotePad
erstellen können, wie dies bei vielen ähnlichen Bibliotheken der Fall wäre. Auf den ersten Blick mag diese Einschränkung ärgerlich erscheinen, aber es war eine Designentscheidung, die Room-Bibliothek an die Architekturbeschränkungen von Android anzupassen. Um diese Entscheidung besser zu verstehen, werfen Sie einen Blick auf die Erklärung von Android für ihren Ansatz.
Obwohl die Objektbeziehung von Room begrenzt ist, existiert sie immer noch. Mit Fremdschlüsseln können übergeordnete und untergeordnete Objekte referenziert und deren Änderungen kaskadiert werden. Beachten Sie, dass es auch empfohlen wird, einen Index für das untergeordnete Objekt zu erstellen, um vollständige Tabellenscans zu vermeiden, wenn das übergeordnete Objekt geändert wird.
@Entity( tableName = "tb_notes", indices = arrayOf( Index( value = *arrayOf("note_date","note_title"), name = "idx_date_title" ), Index( value = *arrayOf("note_pad_id"), name = "idx_pad_note" ) ), foreignKeys = arrayOf( ForeignKey( entity = NotePad::class, parentColumns = arrayOf("pad_id"), childColumns = arrayOf("note_pad_id"), onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE ) ) ) data class Note( @PrimaryKey( autoGenerate = true ) @ColumnInfo( name = "note_id" ) var id: Long, @ColumnInfo( name = "note_title" ) var title: String?, @ColumnInfo( name = "note_text" ) var text: String, @ColumnInfo( name = "note_date" ) var date: Long, @ColumnInfo( name = "note_pad_id") var padId: Long )
Einbetten von Objekten
Es ist möglich, Objekte mit der Annotation @Embedded
in Entitäten einzubetten. Sobald ein Objekt eingebettet ist, werden alle seine Felder als Spalten in der Tabelle der Entität hinzugefügt, wobei die Feldnamen des eingebetteten Objekts als Spaltennamen verwendet werden. Betrachten Sie den folgenden Code.
data class Location( var lat: Float, var lon: Float ) @Entity(tableName = "tb_notes") data class Note( @PrimaryKey( autoGenerate = true ) @ColumnInfo( name = "note_id" ) var id: Long, @Embedded( prefix = "note_location_" ) var location: Location? )
Im obigen Code ist die Location
-Klasse in die Note
-Entität eingebettet. Die Tabelle der Entität hat zwei zusätzliche Spalten, die den Feldern des eingebetteten Objekts entsprechen. Da wir die prefix-Eigenschaft für die @Embedded
-Annotation verwenden, lauten die Namen der Spalten "note_location_lat
" und "note_location_lon
", und es ist möglich, auf diese Spalten in Abfragen zu verweisen.
4. Datenzugriffsobjekt
Um auf die Datenbanken des Room zuzugreifen, ist ein DAO-Objekt erforderlich. Das DAO kann entweder als Schnittstelle oder als abstrakte Klasse definiert werden. Um es zu implementieren, kommentieren Sie die Klasse oder Schnittstelle mit @Dao
und Sie können auf Daten zugreifen. Obwohl es möglich ist, von einem DAO aus auf mehr als eine Tabelle zuzugreifen, wird im Namen einer guten Architektur empfohlen, das Prinzip der Trennung von Bedenken beizubehalten und ein DAO zu erstellen, das für den Zugriff auf jede Entität verantwortlich ist.
@Dao interface NoteDAO{}
Einfügen, aktualisieren und löschen
Raum bietet eine Reihe praktischer Anmerkungen für die CRUD-Operationen im DAO: @Insert
, @Update
, @Delete
und @Query
. Die @Insert
-Operation kann eine einzelne Entität, ein array
oder eine List
von Entitäten als Parameter empfangen. Bei einzelnen Entitäten wird möglicherweise eine long
Zeile zurückgegeben, die die Zeile der Einfügung darstellt. Für mehrere Entitäten als Parameter kann stattdessen ein long[]
oder ein List<Long>
zurückgegeben werden.
@Insert( onConflict = OnConflictStrategy.REPLACE ) fun insertNote(note: Note): Long @Insert( onConflict = OnConflictStrategy.ABORT ) fun insertNotes(notes: List<Note>): List<Long>
Wie Sie sehen, gibt es noch eine weitere Eigenschaft: onConflict
. Dies definiert die Strategie, die bei Konflikten mit OnConflictStrategy
-Konstanten verfolgt werden soll. Die Optionen sind ziemlich selbsterklärend, wobei ABORT
, FAIL
und REPLACE
die wichtigeren Möglichkeiten sind.
Verwenden Sie zum Aktualisieren von Entitäten die Annotation @Update
. Es folgt dem gleichen Prinzip wie @Insert
und empfängt einzelne Entitäten oder mehrere Entitäten als Argumente. Room verwendet die empfangende Entität, um seine Werte zu aktualisieren, wobei der PrimaryKey
der Entität als Referenz verwendet wird. @Update
kann jedoch nur ein int
zurückgeben, das die Gesamtzahl der aktualisierten Tabellenzeilen darstellt.
@Update() fun updateNote(note: Note): Int
Auch hier kann die @Delete
-Annotation nach demselben Prinzip einzelne oder mehrere Entitäten empfangen und ein int
mit der Gesamtzahl der aktualisierten Tabellenzeilen zurückgeben. Es verwendet auch den PrimaryKey
der Entität, um das Register in der Tabelle der Datenbank zu finden und zu entfernen.
@Delete fun deleteNote(note: Note): Int
Anfragen stellen
Schließlich führt die Annotation @Query
Abfragen in der Datenbank durch. Die Abfragen sind ähnlich aufgebaut wie SQLite-Abfragen, wobei der größte Unterschied darin besteht, dass Sie Argumente direkt von den Methoden erhalten können. Die wichtigste Eigenschaft ist jedoch, dass die Abfragen zur Kompilierzeit überprüft werden, dh der Compiler findet einen Fehler, sobald Sie das Projekt erstellen.
Um eine Abfrage zu erstellen, kommentieren Sie eine Methode mit @Query
und schreiben Sie eine SQLite-Abfrage als Wert. Wir werden dem Schreiben von Abfragen nicht allzu viel Aufmerksamkeit schenken, da sie das Standard-SQLite verwenden. Im Allgemeinen verwenden Sie jedoch Abfragen, um mit dem Befehl SELECT
Daten aus der Datenbank abzurufen. Auswahlen können Einzel- oder Sammlungswerte zurückgeben.
@Query("SELECT * FROM tb_notes") fun findAllNotes(): List<Note>
Es ist wirklich einfach, Parameter an Abfragen zu übergeben. Room leitet den Namen des Parameters unter Verwendung des Namens des Methodenarguments ab. Um darauf zuzugreifen, verwenden Sie :
, gefolgt vom Namen.
@Query("SELECT * FROM tb_notes WHERE note_id = :id") fun findNoteById(id: Long): Note @Query(“SELECT * FROM tb_noted WHERE note_date BETWEEN :early AND :late”) fun findNoteByDate(early: Date, late: Date): List<Note>
LiveData-Abfragen
Room wurde entwickelt, um elegant mit LiveData
zu arbeiten. Damit eine @Query
LiveData
zurückgibt, schließen Sie die Standardrückgabe einfach mit LiveData
<?>
Ein und los geht's.
@Query("SELECT * FROM tb_notes WHERE note_id = :id") fun findNoteById(id: Long): LiveData<Note>
Danach ist es möglich, das Abfrageergebnis zu beobachten und ganz einfach asynchrone Ergebnisse zu erhalten. Wenn Sie die Leistungsfähigkeit von LiveData nicht kennen, lesen Sie unser Tutorial über die Komponente.
5. Erstellen der Datenbank
Die Datenbank wird von einer abstrakten Klasse erstellt, die mit @Database
annotiert ist und die RoomDatabase
-Klasse erweitert. Außerdem müssen die Entitäten, die von der Datenbank verwaltet werden, in einem Array in der entities
-Eigenschaft in der @Database
-Annotation übergeben werden.
@Database( entities = arrayOf( NotePad::class, Note::class ) ) abstract class Database : RoomDatabase() { abstract fun padDAO(): PadDAO abstract fun noteDAO(): NoteDAO }
Sobald die Datenbankklasse implementiert ist, ist es an der Zeit, sie zu erstellen. Es ist wichtig zu betonen, dass die Datenbankinstanz idealerweise nur einmal pro Sitzung erstellt werden sollte, und der beste Weg, dies zu erreichen, wäre die Verwendung eines Dependency-Injection-Systems wie Dagger. Wir werden uns jetzt jedoch nicht mit DI befassen, da dies außerhalb des Rahmens dieses Tutorials liegt.
fun providesAppDatabase() : Database { return Room.databaseBuilder( context, Database::class.java, "database") .build() }
Normalerweise können Operationen an einer Room-Datenbank nicht über den UI-Thread ausgeführt werden, da sie blockieren und wahrscheinlich Probleme für das System verursachen. Wenn Sie jedoch die Ausführung im UI-Thread erzwingen möchten, fügen Sie den Build-Optionen allowMainThreadQueries
hinzu. Tatsächlich gibt es viele interessante Optionen zum Erstellen der Datenbank, und ich empfehle Ihnen, die RoomDatabase.Builder
-Dokumentation zu lesen, um die Möglichkeiten zu verstehen.
6. Datentyp und Datenkonvertierung
Ein Spalten-Datentyp wird automatisch von Room definiert. Das System leitet aus dem Feldtyp ab, welche Art von SQLite-Datentyp angemessener ist. Denken Sie daran, dass die meisten POJOs von Java sofort konvertiert werden. Es ist jedoch erforderlich, Datenkonverter zu erstellen, um komplexere Objekte zu verarbeiten, die von Room nicht automatisch erkannt werden, z. B. Date
und Enum
.
Damit Room die Datenkonvertierungen verstehen kann, müssen TypeConverter
bereitgestellt und diese Konverter in Room registriert werden. Es ist möglich, diese Registrierung unter Berücksichtigung eines bestimmten Kontexts vorzunehmen – wenn Sie beispielsweise den TypeConverter
in der Database
registrieren, verwenden alle Entitäten der Datenbank den Konverter. Wenn Sie sich bei einer Entität registrieren, können nur die Eigenschaften dieser Entität sie verwenden usw.
Um ein Date
-Objekt während der Speichervorgänge von Room direkt in ein Long
zu konvertieren und dann beim Konsultieren der Datenbank ein Long
in ein Date
zu konvertieren, deklarieren Sie zuerst einen TypeConverter
.
class DataConverters { @TypeConverter fun fromTimestamp(mills: Long?): Date? { return if (mills == null) null else Date(mills) } @TypeConverter fun fromDate(date: Date?): Long? = date?.time }
Registrieren Sie dann den TypeConverter
in der Database
oder bei Bedarf in einem spezifischeren Kontext.
@Database( entities = arrayOf( NotePad::class, Note::class ), version = 1 ) @TypeConverters(DataConverters::class) abstract class Database : RoomDatabase()
7. Verwenden von Room in einer App
Die Anwendung, die wir in dieser Serie entwickelt haben, verwendet SharedPreferences
, um Wetterdaten zwischenzuspeichern. Da wir nun wissen, wie man Room verwendet, erstellen wir damit einen ausgefeilteren Cache, der es uns ermöglicht, zwischengespeicherte Daten nach Stadt abzurufen und auch das Wetterdatum während des Datenabrufs zu berücksichtigen.
Lassen Sie uns zunächst unsere Entität erstellen. Wir speichern alle unsere Daten nur mit der WeatherMain
-Klasse. Wir müssen der Klasse nur einige Anmerkungen hinzufügen, und wir sind fertig.
@Entity( tableName = "weather" ) data class WeatherMain( @ColumnInfo( name = "date" ) var dt: Long?, @ColumnInfo( name = "city" ) var name: String?, @ColumnInfo(name = "temp_min" ) var tempMin: Double?, @ColumnInfo(name = "temp_max" ) var tempMax: Double?, @ColumnInfo( name = "main" ) var main: String?, @ColumnInfo( name = "description" ) var description: String?, @ColumnInfo( name = "icon" ) var icon: String? ) { @ColumnInfo(name = "id") @PrimaryKey(autoGenerate = true) var id: Long = 0 // ...
Wir brauchen auch einen DAO. Das WeatherDAO
wird die CRUD-Operationen in unserem Unternehmen verwalten. Beachten Sie, dass alle Abfragen LiveData
zurückgeben.
@Dao interface WeatherDAO { @Insert( onConflict = OnConflictStrategy.REPLACE ) fun insert( w: WeatherMain ) @Delete fun remove( w: WeatherMain ) @Query( "SELECT * FROM weather " + "ORDER BY id DESC LIMIT 1" ) fun findLast(): LiveData<WeatherMain> @Query("SELECT * FROM weather " + "WHERE city LIKE :city " + "ORDER BY date DESC LIMIT 1") fun findByCity(city: String ): LiveData<WeatherMain> @Query("SELECT * FROM weather " + "WHERE date < :date " + "ORDER BY date ASC LIMIT 1" ) fun findByDate( date: Long ): List<WeatherMain> }
Schließlich ist es Zeit, die Database
zu erstellen.
@Database( entities = arrayOf(WeatherMain::class), version = 2 ) abstract class Database : RoomDatabase() { abstract fun weatherDAO(): WeatherDAO }
Ok, wir haben jetzt unsere Room-Datenbank konfiguriert. Alles, was Sie noch tun müssen, ist, es mit Dagger
zu verbinden und es zu benutzen. Im DataModule
stellen wir die Database
und das WeatherDAO
bereit.
@Module class DataModule( val context: Context ) { // ... @Provides @Singleton fun providesAppDatabase() : Database { return Room.databaseBuilder( context, Database::class.java, "database") .allowMainThreadQueries() .fallbackToDestructiveMigration() .build() } @Provides @Singleton fun providesWeatherDAO(database: Database) : WeatherDAO { return database.weatherDAO() } }
Wie Sie sich erinnern sollten, verfügen wir über ein Repository, das für die Abwicklung aller Datenvorgänge verantwortlich ist. Lassen Sie uns diese Klasse weiterhin für die Raumdatenanfrage der App verwenden. Aber zuerst müssen wir die Methode providesMainRepository
des DataModule
bearbeiten, um das WeatherDAO
in die Klassenkonstruktion einzubeziehen.
@Module class DataModule( val context: Context ) { //... @Provides @Singleton fun providesMainRepository( openWeatherService: OpenWeatherService, prefsDAO: PrefsDAO, weatherDAO: WeatherDAO, locationLiveData: LocationLiveData ) : MainRepository { return MainRepository( openWeatherService, prefsDAO, weatherDAO, locationLiveData ) } /… }
Die meisten Methoden, die wir dem MainRepository
hinzufügen, sind ziemlich einfach. Es lohnt sich jedoch, clearOldData()
genauer zu betrachten. Dadurch werden alle Daten gelöscht, die älter als einen Tag sind, wobei nur die relevanten Wetterdaten in der Datenbank gespeichert werden.
class MainRepository @Inject constructor( private val openWeatherService: OpenWeatherService, private val prefsDAO: PrefsDAO, private val weatherDAO: WeatherDAO, private val location: LocationLiveData ) : AnkoLogger { fun getWeatherByCity( city: String ) : LiveData<ApiResponse<WeatherResponse>> { info("getWeatherByCity: $city") return openWeatherService.getWeatherByCity(city) } fun saveOnDb( weatherMain: WeatherMain ) { info("saveOnDb:\n$weatherMain") weatherDAO.insert( weatherMain ) } fun getRecentWeather(): LiveData<WeatherMain> { info("getRecentWeather") return weatherDAO.findLast() } fun getRecentWeatherForLocation(location: String): LiveData<WeatherMain> { info("getWeatherByDateAndLocation") return weatherDAO.findByCity(location) } fun clearOldData(){ info("clearOldData") val c = Calendar.getInstance() c.add(Calendar.DATE, -1) // get weather data from 2 days ago val oldData = weatherDAO.findByDate(c.timeInMillis) oldData.forEach{ w -> info("Removing data for '${w.name}':${w.dt}") weatherDAO.remove(w) } } // ... }
Das MainViewModel
ist verantwortlich für die Konsultationen zu unserem Repository. Fügen wir der Room-Datenbank eine Logik hinzu, um unsere Vorgänge zu adressieren. Zuerst fügen wir eine MutableLiveData
hinzu, die weatherDB
, die für die Abfrage des MainRepository
zuständig ist. Dann entfernen wir Verweise auf SharedPreferences
, sodass unser Cache nur auf die Room-Datenbank angewiesen ist.
class MainViewModel @Inject constructor( private val repository: MainRepository ) : ViewModel(), AnkoLogger { // … // Weather saved on database private var weatherDB: LiveData<WeatherMain> = MutableLiveData() // … // We remove the consultation to SharedPreferences // making the cache exclusive to Room private fun getWeatherCached() { info("getWeatherCached") weatherDB = repository.getRecentWeather() weather.addSource( weatherDB, { w -> info("weatherDB: DB: \n$w") weather.postValue(ApiResponse(data = w)) weather.removeSource(weatherDBSaved) } ) }
Um unseren Cache relevant zu machen, löschen wir bei jeder neuen Wetterkonsultation alte Daten.
private var weatherByLocationResponse: LiveData<ApiResponse<WeatherResponse>> = Transformations.switchMap( location, { l -> info("weatherByLocation: \nlocation: $l") doAsync { repository.clearOldData() } return@switchMap repository.getWeatherByLocation(l) } ) private var weatherByCityResponse: LiveData<ApiResponse<WeatherResponse>> = Transformations.switchMap( cityName, { city -> info("weatherByCityResponse: city: $city") doAsync { repository.clearOldData() } return@switchMap repository.getWeatherByCity(city) } )
Schließlich speichern wir die Daten jedes Mal in der Room-Datenbank, wenn neues Wetter eingeht.
// Receives updated weather response, // send it to UI and also save it private fun updateWeather(w: WeatherResponse){ info("updateWeather") // getting weather from today val weatherMain = WeatherMain.factory(w) // save on shared preferences repository.saveWeatherMainOnPrefs(weatherMain) // save on db repository.saveOnDb(weatherMain) // update weather value weather.postValue(ApiResponse(data = weatherMain)) }
Sie können den vollständigen Code im GitHub-Repository für diesen Beitrag sehen.
Abschluss
Endlich sind wir am Ende der Android Architecture Components-Reihe angelangt. Diese Tools werden ausgezeichnete Begleiter auf Ihrer Android-Entwicklungsreise sein. Ich rate Ihnen, die Komponenten weiter zu erkunden. Nehmen Sie sich etwas Zeit, um die Dokumentation zu lesen.
Und sieh dir einige unserer anderen Beiträge zur Android-App-Entwicklung hier auf Envato Tuts+ an!
- Android SDKVereinfachen Sie die Entwicklung von Android-Apps mit AnkoAshraff Hathibelagal
- Android SDKSo codieren Sie die Verarbeitung natürlicher Sprache unter Android mit IBM WatsonAshraff Hathibelagal
- KotlinKotlin von Grund auf neu: Mehr Spaß mit FunktionenChike Mgbemena
- Android SDKParallelität und Coroutinen in KotlinAshraff Hathibelagal