Dieses Codelab ist Teil des Kurses „Grundlagen von Android und Kotlin“. Sie können diesen Kurs am besten nutzen, wenn Sie die Codelabs der Reihe nach durcharbeiten. Alle Codelabs des Kurses sind auf der Landingpage für Codelabs zu den Grundlagen von Android und Kotlin aufgeführt.
Einführung
Eine der wichtigsten Prioritäten bei der Entwicklung einer nutzerfreundlichen App ist es, dafür zu sorgen, dass die Benutzeroberfläche immer reagiert und reibungslos funktioniert. Eine Möglichkeit, die Leistung der Benutzeroberfläche zu verbessern, besteht darin, zeitaufwendige Aufgaben wie Datenbankvorgänge in den Hintergrund zu verlagern.
In diesem Codelab implementierst du den nutzerorientierten Teil der App „TrackMySleepQuality“ und verwendest Kotlin-Coroutinen, um Datenbankvorgänge außerhalb des Hauptthreads auszuführen.
Was Sie bereits wissen sollten
Sie sollten mit Folgendem vertraut sein:
- Erstellen einer einfachen Benutzeroberfläche (UI) mit einer Aktivität, Fragmenten, Ansichten und Click-Handlern.
- Zwischen Fragmenten navigieren und
safeArgs
verwenden, um einfache Daten zwischen Fragmenten zu übergeben. - Modelle, Modell-Factories, Transformationen und
LiveData
ansehen - So erstellen Sie eine
Room
-Datenbank, ein DAO und definieren Entitäten. - Es ist hilfreich, wenn Sie mit den Konzepten von Threads und Multiprocessing vertraut sind.
Lerninhalte
- Funktionsweise von Threads in Android
- So verwenden Sie Kotlin-Coroutinen, um Datenbankvorgänge vom Hauptthread zu trennen.
- So werden formatierte Daten in einem
TextView
angezeigt.
Aufgaben
- Erweitern Sie die App „TrackMySleepQuality“, um Daten in der Datenbank zu erfassen, zu speichern und anzuzeigen.
- Verwenden Sie Coroutinen, um lange laufende Datenbankvorgänge im Hintergrund auszuführen.
- Verwenden Sie
LiveData
, um die Navigation und die Anzeige einer Snackbar auszulösen. - Mit
LiveData
können Sie Schaltflächen aktivieren und deaktivieren.
In diesem Codelab erstellen Sie das View-Modell, die Coroutinen und die Teile der TrackMySleepQuality-App, die für die Datenanzeige zuständig sind.
Die App hat zwei Bildschirme, die durch Fragmente dargestellt werden, wie in der Abbildung unten zu sehen ist.
Auf dem ersten Bildschirm links befinden sich Schaltflächen zum Starten und Beenden des Trackings. Auf dem Bildschirm werden alle Schlafdaten des Nutzers angezeigt. Mit der Schaltfläche Löschen werden alle Daten, die die App für den Nutzer erhoben hat, dauerhaft gelöscht.
Auf dem zweiten Bildschirm rechts können Sie eine Bewertung der Schlafqualität auswählen. In der App wird die Bewertung numerisch dargestellt. Zu Entwicklungszwecken werden in der App sowohl die Gesichtssymbole als auch ihre numerischen Entsprechungen angezeigt.
Der Ablauf für den Nutzer sieht so aus:
- Der Nutzer öffnet die App und sieht den Bildschirm für die Schlafanalyse.
- Der Nutzer tippt auf die Schaltfläche Starten. Die Startzeit wird aufgezeichnet und angezeigt. Die Schaltfläche Starten ist deaktiviert und die Schaltfläche Beenden ist aktiviert.
- Der Nutzer tippt auf die Schaltfläche Stopp. Dadurch wird die Endzeit aufgezeichnet und der Bildschirm „Schlafqualität“ geöffnet.
- Der Nutzer wählt ein Symbol für die Schlafqualität aus. Der Bildschirm wird geschlossen und auf dem Tracking-Bildschirm werden die Endzeit des Schlafs und die Schlafqualität angezeigt. Die Schaltfläche Beenden ist deaktiviert und die Schaltfläche Starten ist aktiviert. Die App ist bereit für eine weitere Nacht.
- Die Schaltfläche Löschen ist immer dann aktiviert, wenn Daten in der Datenbank vorhanden sind. Wenn der Nutzer auf die Schaltfläche Löschen tippt, werden alle seine Daten unwiderruflich gelöscht. Es wird keine Bestätigungsmeldung angezeigt.
Diese App verwendet eine vereinfachte Architektur, wie unten im Kontext der vollständigen Architektur dargestellt. Die App verwendet nur die folgenden Komponenten:
- UI-Controller
- Modell und
LiveData
ansehen - Eine Room-Datenbank
In dieser Aufgabe verwenden Sie ein TextView
, um formatierte Daten zum Schlaftracking anzuzeigen. Dies ist nicht die endgültige Benutzeroberfläche. In einem anderen Codelab erfahren Sie, wie Sie das besser machen können.)
Sie können mit der App „TrackMySleepQuality“ fortfahren, die Sie im vorherigen Codelab erstellt haben, oder die Starter-App für dieses Codelab herunterladen.
Schritt 1: Start-App herunterladen und ausführen
- Laden Sie die App TrackMySleepQuality-Coroutines-Starter von GitHub herunter.
- Erstellen Sie die App und führen Sie sie aus. In der App wird die Benutzeroberfläche für das Fragment
SleepTrackerFragment
angezeigt, aber keine Daten. Die Tasten reagieren nicht auf Antippen.
Schritt 2: Code prüfen
Der Startcode für dieses Codelab ist derselbe wie der Lösungscode für das Codelab 6.1 „Room-Datenbank erstellen“.
- Öffnen Sie res/layout/activity_main.xml. Dieses Layout enthält das Fragment
nav_host_fragment
. Beachten Sie auch das<merge>
-Tag.
Mit demmerge
-Tag können Sie beim Einbinden von Layouts redundante Layouts vermeiden. Es empfiehlt sich, es zu verwenden. Ein Beispiel für ein redundantes Layout wäre ConstraintLayout > LinearLayout > TextView, wobei das System möglicherweise das LinearLayout eliminieren kann. Diese Art der Optimierung kann die Ansichtshierarchie vereinfachen und die App-Leistung verbessern. - Öffnen Sie im Ordner navigation die Datei navigation.xml. Sie sehen zwei Fragmente und die Navigationsaktionen, die sie verbinden.
- Doppelklicken Sie im Ordner layout auf das Fragment für den Schlaftracker, um das XML-Layout aufzurufen. Beachten Sie Folgendes:
- Die Layoutdaten sind in einem
<layout>
-Element eingeschlossen, um die Datenbindung zu ermöglichen. ConstraintLayout
und die anderen Ansichten sind im Element<layout>
angeordnet.- Die Datei enthält ein
<data>
-Platzhalter-Tag.
Die Starter-App enthält auch Dimensionen, Farben und Formatierungen für die Benutzeroberfläche. Die App enthält eine Room
-Datenbank, ein DAO und eine SleepNight
-Entität. Wenn Sie das vorherige Codelab nicht durchgearbeitet haben, sollten Sie sich diese Aspekte des Codes selbst ansehen.
Nachdem Sie eine Datenbank und eine Benutzeroberfläche haben, müssen Sie Daten erheben, sie der Datenbank hinzufügen und anzeigen. All diese Arbeit wird im Ansichtsmodell erledigt. Das View-Modell für den Schlaftracker verarbeitet Schaltflächenklicks, interagiert über das DAO mit der Datenbank und stellt der Benutzeroberfläche Daten über LiveData
zur Verfügung. Alle Datenbankvorgänge müssen außerhalb des Haupt-UI-Threads ausgeführt werden. Dazu verwenden Sie Coroutinen.
Schritt 1: SleepTrackerViewModel hinzufügen
- Öffnen Sie im Paket sleeptracker die Datei SleepTrackerViewModel.kt.
- Sehen Sie sich die Klasse
SleepTrackerViewModel
an, die in der Starter-App enthalten ist und auch unten angezeigt wird. Die Klasse erweitertAndroidViewModel()
. Diese Klasse entsprichtViewModel
, verwendet jedoch den Anwendungskontext als Parameter und macht ihn als Property verfügbar. Sie benötigen ihn später.
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
Schritt 2: SleepTrackerViewModelFactory hinzufügen
- Öffnen Sie im Paket sleeptracker die Datei SleepTrackerViewModelFactory.kt.
- Sehen Sie sich den Code für die Factory an, der unten aufgeführt ist:
class SleepTrackerViewModelFactory(
private val dataSource: SleepDatabaseDao,
private val application: Application) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
return SleepTrackerViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Beachten Sie Folgendes:
- Die bereitgestellte
SleepTrackerViewModelFactory
verwendet dasselbe Argument wieViewModel
und erweitertViewModelProvider.Factory
. - In der Factory wird
create()
überschrieben. Diese Funktion akzeptiert einen beliebigen Klassentyp als Argument und gibt einViewModel
zurück. - Im Text von
create()
wird geprüft, ob eineSleepTrackerViewModel
-Klasse verfügbar ist. Wenn dies der Fall ist, wird eine Instanz davon zurückgegeben. Andernfalls wird eine Ausnahme ausgegeben.
Schritt 3: SleepTrackerFragment
aktualisieren
- Rufen Sie im
SleepTrackerFragment
eine Referenz zum Anwendungskontext ab. Fügen Sie die Referenz inonCreateView()
unterbinding
ein. Sie benötigen einen Verweis auf die App, an die dieses Fragment angehängt ist, um ihn an den Anbieter der ViewModel-Factory zu übergeben.
Die Kotlin-FunktionrequireNotNull
löst eineIllegalArgumentException
aus, wenn der Wertnull
ist.
val application = requireNotNull(this.activity).application
- Sie benötigen einen Verweis auf Ihre Datenquelle über einen Verweis auf das DAO. Definieren Sie in
onCreateView()
vor demreturn
eindataSource
. Verwenden SieSleepDatabase.getInstance(application).sleepDatabaseDao
, um einen Verweis auf das DAO der Datenbank abzurufen.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- Erstellen Sie in
onCreateView()
vor demreturn
eine Instanz vonviewModelFactory
. Sie müssendataSource
undapplication
übergeben.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- Nachdem Sie eine Factory haben, rufen Sie eine Referenz auf
SleepTrackerViewModel
ab. Der ParameterSleepTrackerViewModel::class.java
bezieht sich auf die Java-Laufzeitklasse dieses Objekts.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- Der fertige Code sollte so aussehen:
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
So sieht die onCreateView()
-Methode bisher aus:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_sleep_tracker, container, false)
val application = requireNotNull(this.activity).application
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
return binding.root
}
Schritt 4: Datenbindung für das Ansichtsmodell hinzufügen
Nachdem Sie die grundlegende ViewModel
eingerichtet haben, müssen Sie die Einrichtung der Datenbindung in der SleepTrackerFragment
abschließen, um die ViewModel
mit der Benutzeroberfläche zu verbinden.
In der Layoutdatei fragment_sleep_tracker.xml
:
- Erstellen Sie im Block
<data>
ein<variable>
, das auf die KlasseSleepTrackerViewModel
verweist.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
In SleepTrackerFragment
:
- Die aktuelle Aktivität als Lebenszyklusinhaber der Bindung festlegen. Fügen Sie diesen Code in die
onCreateView()
-Methode vor derreturn
-Anweisung ein:
binding.setLifecycleOwner(this)
- Weisen Sie die Bindungsvariable
sleepTrackerViewModel
dersleepTrackerViewModel
zu. Fügen Sie diesen Code inonCreateView()
ein, unter den Code, mit demSleepTrackerViewModel
erstellt wird:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- Wahrscheinlich wird ein Fehler angezeigt, da Sie das Bindungsobjekt neu erstellen müssen. Bereinigen Sie das Projekt und erstellen Sie es neu, um den Fehler zu beheben.
- Achten Sie wie immer darauf, dass Ihr Code ohne Fehler erstellt und ausgeführt wird.
In Kotlin sind Coroutinen die Möglichkeit, lang andauernde Aufgaben elegant und effizient zu verarbeiten. Mit Kotlin-Coroutinen können Sie Callback-basierten Code in sequenziellen Code umwandeln. Sequenziell geschriebener Code ist in der Regel leichter zu lesen und kann sogar Sprachfunktionen wie Ausnahmen verwenden. Letztendlich tun Coroutinen und Callbacks dasselbe: Sie warten, bis ein Ergebnis von einer zeitaufwendigen Aufgabe verfügbar ist, und setzen die Ausführung fort.
Coroutinen haben die folgenden Eigenschaften:
- Coroutinen sind asynchron und nicht blockierend.
- Bei Coroutinen werden suspend-Funktionen verwendet, um asynchronen Code sequenziell zu machen.
Coroutinen sind asynchron.
Eine Coroutine wird unabhängig von den Hauptausführungsschritten Ihres Programms ausgeführt. Dies kann parallel oder auf einem separaten Prozessor erfolgen. Es könnte auch sein, dass Sie ein wenig Verarbeitung einfügen, während der Rest der App auf Eingaben wartet. Einer der wichtigsten Aspekte von „async“ ist, dass Sie nicht davon ausgehen können, dass das Ergebnis verfügbar ist, bis Sie explizit darauf warten.
Angenommen, Sie haben eine Frage, die Recherche erfordert, und Sie bitten einen Kollegen, die Antwort zu finden. Sie machen sich an die Arbeit, was so viel bedeutet, als würden sie die Arbeit „asynchron“ und „in einem separaten Thread“ erledigen. Sie können mit anderen Aufgaben fortfahren, die nicht von der Antwort abhängen, bis Ihr Kollege zurückkommt und Ihnen die Antwort mitteilt.
Coroutinen sind nicht blockierend.
Nicht blockierend bedeutet, dass eine Coroutine den Haupt- oder UI-Thread nicht blockiert. Mit Coroutinen wird die Benutzeroberfläche immer priorisiert, sodass Nutzer immer die bestmögliche Erfahrung haben.
Bei Coroutinen werden suspend-Funktionen verwendet, um asynchronen Code sequenziell zu machen.
Das Keyword suspend
ist die Kotlin-Methode, um eine Funktion oder einen Funktionstyp als für Coroutinen verfügbar zu kennzeichnen. Wenn eine Coroutine eine mit suspend
markierte Funktion aufruft, wird die Ausführung der Coroutine nicht wie bei einem normalen Funktionsaufruf blockiert, bis die Funktion zurückkehrt. Stattdessen wird die Ausführung der Coroutine angehalten, bis das Ergebnis verfügbar ist. Die Coroutine wird dann mit dem Ergebnis an der Stelle fortgesetzt, an der sie unterbrochen wurde.
Während die Coroutine angehalten wird und auf ein Ergebnis wartet, wird der Thread, auf dem sie ausgeführt wird, nicht blockiert. So können andere Funktionen oder Coroutinen ausgeführt werden.
Das Keyword suspend
gibt nicht den Thread an, in dem der Code ausgeführt wird. Eine Suspend-Funktion kann in einem Hintergrundthread oder im Hauptthread ausgeführt werden.
Um Coroutinen in Kotlin zu verwenden, benötigen Sie drei Dinge:
- Ein Job
- Ein Disponent
- Ein Bereich
Job: Ein Job ist im Grunde alles, was abgebrochen werden kann. Jede Coroutine hat einen Job, mit dem Sie die Coroutine abbrechen können. Jobs können in hierarchischen Beziehungen angeordnet werden. Wenn Sie einen übergeordneten Job abbrechen, werden alle untergeordneten Jobs sofort abgebrochen. Das ist viel praktischer, als jede Coroutine manuell abzubrechen.
Dispatcher : Der Dispatcher sendet Coroutinen zur Ausführung in verschiedenen Threads. Beispielsweise führt Dispatcher.Main
Aufgaben im Hauptthread aus und Dispatcher.IO
lagert blockierende E/A-Aufgaben in einen gemeinsamen Thread-Pool aus.
Umfang : Der Umfang einer Coroutine definiert den Kontext, in dem die Coroutine ausgeführt wird. Ein Bereich kombiniert Informationen zum Job und Dispatcher einer Coroutine. Mit Scopes werden Coroutinen im Blick behalten. Wenn Sie eine Coroutine starten, befindet sie sich in einem Bereich. Das bedeutet, dass Sie angegeben haben, welcher Bereich die Coroutine im Blick behält.
Der Nutzer soll auf folgende Weise mit den Schlafdaten interagieren können:
- Wenn der Nutzer auf die Schaltfläche Start tippt, erstellt die App eine neue Schlafnacht und speichert sie in der Datenbank.
- Wenn der Nutzer auf die Schaltfläche Beenden tippt, aktualisiert die App die Nacht mit einer Endzeit.
- Wenn der Nutzer auf die Schaltfläche Löschen tippt, löscht die App die Daten in der Datenbank.
Diese Datenbankvorgänge können lange dauern und sollten daher in einem separaten Thread ausgeführt werden.
Schritt 1: Coroutinen für Datenbankvorgänge einrichten
Wenn in der Sleep Tracker App auf die Schaltfläche Start getippt wird, soll eine Funktion in SleepTrackerViewModel
aufgerufen werden, um eine neue Instanz von SleepNight
zu erstellen und in der Datenbank zu speichern.
Durch Tippen auf eine der Schaltflächen wird ein Datenbankvorgang ausgelöst, z. B. das Erstellen oder Aktualisieren einer SleepNight
. Aus diesem und anderen Gründen verwenden Sie Coroutinen, um Klick-Handler für die Schaltflächen der App zu implementieren.
- Öffnen Sie die Datei
build.gradle
auf App-Ebene und suchen Sie nach den Abhängigkeiten für Coroutinen. Wenn Sie Coroutinen verwenden möchten, benötigen Sie diese Abhängigkeiten, die für Sie hinzugefügt wurden.
Die$coroutine_version
ist in der Projektdateibuild.gradle
alscoroutine_version =
'1.0.0'
definiert.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
- Öffnen Sie die Datei
SleepTrackerViewModel
. - Definieren Sie im Text der Klasse
viewModelJob
und weisen Sie ihr eine Instanz vonJob
zu. Mit diesemviewModelJob
können Sie alle von diesem ViewModel gestarteten Coroutinen abbrechen, wenn das ViewModel nicht mehr verwendet und zerstört wird. So vermeiden Sie, dass Koroutinen ohne Rückkehrziel entstehen.
private var viewModelJob = Job()
- Überschreiben Sie am Ende des Hauptteils der Klasse
onCleared()
und brechen Sie alle Coroutinen ab. Wenn dasViewModel
zerstört wird, wirdonCleared()
aufgerufen.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- Definieren Sie direkt unter der Definition von
viewModelJob
einenuiScope
für die Coroutinen. Der Bereich bestimmt, in welchem Thread die Coroutine ausgeführt wird, und der Bereich muss auch über den Job informiert sein. Wenn Sie einen Bereich abrufen möchten, fordern Sie eine Instanz vonCoroutineScope
an und übergeben Sie einen Dispatcher und einen Job.
Wenn Sie Dispatchers.Main
verwenden, werden im uiScope
gestartete Coroutinen im Hauptthread ausgeführt. Das ist für viele von einem ViewModel
gestartete Coroutinen sinnvoll, da sie nach der Verarbeitung zu einer Aktualisierung der Benutzeroberfläche führen.
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
- Definieren Sie unter der Definition von
uiScope
eine Variable mit dem Namentonight
, die die aktuelle Nacht enthält. Machen Sie die VariableMutableLiveData
, da Sie die Daten beobachten und ändern müssen.
private var tonight = MutableLiveData<SleepNight?>()
- Um die Variable
tonight
so schnell wie möglich zu initialisieren, erstellen Sie eineninit
-Block unter der Definition vontonight
und rufen SieinitializeTonight()
auf. Sie definiereninitializeTonight()
im nächsten Schritt.
init {
initializeTonight()
}
- Implementieren Sie unter dem
init
-BlockinitializeTonight()
. Starten Sie imuiScope
eine Koroutine. Rufen Sie den Wert fürtonight
aus der Datenbank ab, indem SiegetTonightFromDatabase()
aufrufen, und weisen Sie den Werttonight.value
zu. Sie definierengetTonightFromDatabase()
im nächsten Schritt.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
getTonightFromDatabase()
implementieren. Definieren Sie sie alsprivate suspend
-Funktion, die ein nullableSleepNight
zurückgibt, wenn kein aktuellesSleepNight
gestartet wurde. Das führt zu einem Fehler, da die Funktion etwas zurückgeben muss.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- Geben Sie im Funktionskörper von
getTonightFromDatabase()
das Ergebnis einer Coroutine zurück, die imDispatchers.IO
-Kontext ausgeführt wird. Verwende den E/A-Dispatcher, da das Abrufen von Daten aus der Datenbank ein E/A-Vorgang ist und nichts mit der Benutzeroberfläche zu tun hat.
return withContext(Dispatchers.IO) {}
- Lassen Sie die Coroutine im Rückgabeblock die heutige Nacht (die neueste Nacht) aus der Datenbank abrufen. Wenn die Start- und Endzeit nicht identisch sind, was bedeutet, dass die Nacht bereits abgeschlossen ist, gib
null
zurück. Andernfalls wird die Nacht zurückgegeben.
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
Die fertige getTonightFromDatabase()
-Funktion zum Sperren sollte so aussehen. Es sollten keine Fehler mehr auftreten.
private suspend fun getTonightFromDatabase(): SleepNight? {
return withContext(Dispatchers.IO) {
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
}
}
Schritt 2: Klick-Handler für die Schaltfläche „Start“ hinzufügen
Jetzt können Sie onStartTracking()
implementieren, den Click-Handler für die Schaltfläche Start. Sie müssen ein neues SleepNight
erstellen, es in die Datenbank einfügen und tonight
zuweisen. Die Struktur von onStartTracking()
wird initializeTonight()
sehr ähnlich sein.
- Beginnen Sie mit der Funktionsdefinition für
onStartTracking()
. Sie können die Click-Handler überonCleared()
in der DateiSleepTrackerViewModel
platzieren.
fun onStartTracking() {}
- Starten Sie in
onStartTracking()
eine Coroutine inuiScope
, da Sie dieses Ergebnis benötigen, um fortzufahren und die Benutzeroberfläche zu aktualisieren.
uiScope.launch {}
- Erstellen Sie innerhalb des Coroutine-Starts ein neues
SleepNight
, das die aktuelle Zeit als Startzeit erfasst.
val newNight = SleepNight()
- Rufen Sie innerhalb des Coroutine-Starts
insert()
auf, umnewNight
in die Datenbank einzufügen. Es wird ein Fehler angezeigt, da Sie die Funktioninsert()
noch nicht definiert haben. (Dies ist nicht die DAO-Funktion mit demselben Namen.)
insert(newNight)
- Aktualisieren Sie
tonight
auch innerhalb des Coroutine-Starts.
tonight.value = getTonightFromDatabase()
- Definieren Sie unter
onStartTracking()
die Funktioninsert()
alsprivate suspend
-Funktion, die einSleepNight
als Argument verwendet.
private suspend fun insert(night: SleepNight) {}
- Starte für den Text von
insert()
eine Coroutine im I/O-Kontext und füge die Nacht in die Datenbank ein, indem duinsert()
über das DAO aufrufst.
withContext(Dispatchers.IO) {
database.insert(night)
}
- Fügen Sie in der Layoutdatei
fragment_sleep_tracker.xml
den Click-Handler füronStartTracking()
instart_button
ein. Verwenden Sie dazu die Datenbindung, die Sie zuvor eingerichtet haben. Mit der Funktionsnotation@{() ->
wird eine Lambda-Funktion erstellt, die keine Argumente akzeptiert und den Klick-Handler insleepTrackerViewModel
aufruft.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- Erstellen Sie Ihre App und führen Sie sie aus. Tippen Sie dazu auf die Schaltfläche Starten. Durch diese Aktion werden Daten erstellt, aber Sie können noch nichts sehen. Das beheben Sie als Nächstes.
fun someWorkNeedsToBeDone { uiScope.launch { suspendFunction() } } suspend fun suspendFunction() { withContext(Dispatchers.IO) { longrunningWork() } }
Schritt 3: Daten darstellen
Im SleepTrackerViewModel
verweist die Variable nights
auf LiveData
, da getAllNights()
im DAO LiveData
zurückgibt.
Es ist eine Room
-Funktion, bei der die LiveData
nights
jedes Mal aktualisiert wird, wenn sich die Daten in der Datenbank ändern, um die neuesten Daten anzuzeigen. Sie müssen die LiveData
nie explizit festlegen oder aktualisieren. Room
aktualisiert die Daten entsprechend der Datenbank.
Wenn Sie nights
jedoch in einer Textansicht anzeigen, wird die Objektreferenz angezeigt. Wenn Sie den Inhalt des Objekts sehen möchten, müssen Sie die Daten in einen formatierten String umwandeln. Verwenden Sie eine Transformation
-Zuordnung, die jedes Mal ausgeführt wird, wenn nights
neue Daten aus der Datenbank empfängt.
- Öffnen Sie die Datei
Util.kt
und entfernen Sie die Kommentarzeichen für den Code für die Definition vonformatNights()
und die zugehörigenimport
-Anweisungen. Wenn Sie in Android Studio Code auskommentieren möchten, wählen Sie den gesamten mit//
gekennzeichneten Code aus und drücken SieCmd+/
oderControl+/
. formatNights()
gibt den TypSpanned
zurück, einen HTML-formatierten String.- Öffnen Sie strings.xml. Beachten Sie die Verwendung von
CDATA
zum Formatieren der String-Ressourcen für die Anzeige der Schlafdaten. - Öffne SleepTrackerViewModel. Definieren Sie in der Klasse
SleepTrackerViewModel
unter der Definition vonuiScope
eine Variable mit dem Namennights
. Rufen Sie alle Nächte aus der Datenbank ab und weisen Sie sie der Variablennights
zu.
private val nights = database.getAllNights()
- Fügen Sie direkt unter der Definition von
nights
Code hinzu, umnights
in einenightsString
umzuwandeln. Verwenden Sie die FunktionformatNights()
ausUtil.kt
.
. Übergeben Sienights
an die Funktionmap()
aus der KlasseTransformations
. Wenn Sie auf Ihre String-Ressourcen zugreifen möchten, definieren Sie die Zuordnungsfunktion als Aufruf vonformatNights()
. Geben Sienights
und einResources
-Objekt an.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- Öffnen Sie die Layoutdatei
fragment_sleep_tracker.xml
. In derTextView
können Sie in der Propertyandroid:text
den Ressourcenstring durch einen Verweis aufnightsString
ersetzen.
"@{sleepTrackerViewModel.nightsString}"
- Erstelle den Code neu und führe die App aus. Alle deine Schlafdaten mit Startzeiten sollten jetzt angezeigt werden.
- Tippe noch einige Male auf die Schaltfläche Starten, um weitere Daten zu sehen.
Im nächsten Schritt aktivieren Sie die Funktion für die Schaltfläche Stop.
Schritt 4: Klick-Handler für die Schaltfläche „Stopp“ hinzufügen
Implementieren Sie den Klick-Handler für die Schaltfläche Stop in SleepTrackerViewModel.
. Verwenden Sie dabei dasselbe Muster wie im vorherigen Schritt.
- Fügen Sie
onStopTracking()
zuViewModel
hinzu. Starten Sie eine Koroutine imuiScope
. Wenn die Endzeit noch nicht festgelegt wurde, legen SieendTimeMilli
auf die aktuelle Systemzeit fest und rufen Sieupdate()
mit den Daten für die Nacht auf.
In Kotlin gibt die Syntaxreturn@
label
die Funktion an, aus der diese Anweisung zurückgegeben wird, wenn mehrere Funktionen verschachtelt sind.
fun onStopTracking() {
uiScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
- Implementieren Sie
update()
mit demselben Muster wie fürinsert()
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- Um den Click-Handler mit der Benutzeroberfläche zu verbinden, öffnen Sie die Layoutdatei
fragment_sleep_tracker.xml
und fügen Sie den Click-Handler demstop_button
hinzu.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- Erstellen Sie Ihre App und führen Sie sie aus.
- Tippe auf Starten und dann auf Beenden. Du siehst die Start- und Endzeit, die Schlafqualität ohne Wert und die geschlafene Zeit.
Schritt 5: Klick-Handler für die Schaltfläche „Löschen“ hinzufügen
- Implementieren Sie
onClear()
undclear()
auf ähnliche Weise.
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- Um den Click-Handler mit der Benutzeroberfläche zu verbinden, öffnen Sie
fragment_sleep_tracker.xml
und fügen Sie den Click-Handler zuclear_button
hinzu.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- Erstellen Sie Ihre App und führen Sie sie aus.
- Tippen Sie auf Löschen, um alle Daten zu entfernen. Tippe dann auf Starten und Beenden, um neue Daten zu erstellen.
Android Studio-Projekt: TrackMySleepQualityCoroutines
- Verwenden Sie
ViewModel
,ViewModelFactory
und die Datenbindung, um die UI-Architektur für die App einzurichten. - Damit die Benutzeroberfläche reibungslos funktioniert, sollten Sie für zeitaufwendige Aufgaben wie alle Datenbankvorgänge Coroutinen verwenden.
- Coroutinen sind asynchron und nicht blockierend. Sie verwenden
suspend
-Funktionen, um asynchronen Code sequenziell zu machen. - Wenn eine Coroutine eine mit
suspend
markierte Funktion aufruft, wird die Ausführung nicht wie bei einem normalen Funktionsaufruf blockiert, bis die Funktion zurückgegeben wird, sondern die Ausführung wird angehalten, bis das Ergebnis verfügbar ist. Anschließend wird die Ausführung mit dem Ergebnis fortgesetzt. - Der Unterschied zwischen Blockieren und Anhalten besteht darin, dass bei einem blockierten Thread keine weiteren Aktionen ausgeführt werden. Wenn der Thread angehalten wird, werden andere Aufgaben ausgeführt, bis das Ergebnis verfügbar ist.
Zum Starten einer Coroutine benötigen Sie einen Job, einen Dispatcher und einen Bereich:
- Ein Job ist im Grunde alles, was abgebrochen werden kann. Jede Coroutine hat einen Job und Sie können einen Job verwenden, um eine Coroutine abzubrechen.
- Der Dispatcher sendet Coroutinen, die in verschiedenen Threads ausgeführt werden.
Dispatcher.Main
führt Aufgaben im Hauptthread aus undDispartcher.IO
dient dazu, blockierende E/A-Aufgaben an einen gemeinsamen Thread-Pool auszulagern. - Der Scope kombiniert Informationen, einschließlich eines Jobs und eines Dispatchers, um den Kontext zu definieren, in dem die Coroutine ausgeführt wird. Mit Scopes werden Coroutinen im Blick behalten.
So implementieren Sie Klick-Handler, die Datenbankvorgänge auslösen:
- Starten Sie eine Coroutine, die im Haupt- oder UI-Thread ausgeführt wird, da sich das Ergebnis auf die Benutzeroberfläche auswirkt.
- Rufen Sie eine Suspend-Funktion auf, um die zeitaufwendige Arbeit zu erledigen, damit der UI-Thread nicht blockiert wird, während Sie auf das Ergebnis warten.
- Die lang andauernde Arbeit hat nichts mit der Benutzeroberfläche zu tun. Wechseln Sie also zum E/A-Kontext. So kann die Arbeit in einem Threadpool ausgeführt werden, der für diese Art von Vorgängen optimiert und reserviert ist.
- Rufen Sie dann die Datenbankfunktion auf, um die Aufgabe auszuführen.
Verwenden Sie eine Transformations
-Zuordnung, um jedes Mal, wenn sich das Objekt ändert, einen String aus einem LiveData
-Objekt zu erstellen.
Udacity-Kurs:
Android-Entwicklerdokumentation:
RoomDatabase
- Layouts mit <include/> wiederverwenden
ViewModelProvider.Factory
SimpleDateFormat
HtmlCompat
Weitere Dokumentation und Artikel:
- Fabrikmuster
- Codelab zu Coroutinen
- Coroutines, offizielle Dokumentation
- Coroutine-Kontext und Dispatcher
Dispatchers
- Android-Geschwindigkeitsbegrenzung überschreiten
Job
launch
- Rückgabe und Sprünge in Kotlin
- CDATA steht für Zeichendaten. CDATA bedeutet, dass die Daten zwischen diesen Strings Daten enthalten, die als XML-Markup interpretiert werden könnten, aber nicht sollten.
In diesem Abschnitt werden mögliche Hausaufgaben für Schüler und Studenten aufgeführt, die dieses Codelab im Rahmen eines von einem Kursleiter geleiteten Kurses durcharbeiten. Es liegt in der Verantwortung des Kursleiters, Folgendes zu tun:
- Weisen Sie bei Bedarf Aufgaben zu.
- Teilen Sie den Schülern/Studenten mit, wie sie Hausaufgaben abgeben können.
- Benoten Sie die Hausaufgaben.
Lehrkräfte können diese Vorschläge nach Belieben nutzen und auch andere Hausaufgaben zuweisen, die sie für angemessen halten.
Wenn Sie dieses Codelab selbst durcharbeiten, können Sie mit diesen Hausaufgaben Ihr Wissen testen.
Beantworten Sie diese Fragen
Frage 1
Welche der folgenden Optionen sind Vorteile von Coroutinen?
- Sie sind nicht blockierend.
- Sie werden asynchron ausgeführt.
- Sie können in einem anderen Thread als dem Haupt-Thread ausgeführt werden.
- Dadurch laufen Apps immer schneller.
- Mit Ausnahmen
- Sie können als linearer Code geschrieben und gelesen werden.
Frage 2
Was ist eine Suspend-Funktion?
- Eine normale Funktion, die mit dem Keyword
suspend
annotiert ist. - Eine Funktion, die in Koroutinen aufgerufen werden kann.
- Während eine suspend-Funktion ausgeführt wird, wird der aufrufende Thread angehalten.
- Suspend-Funktionen müssen immer im Hintergrund ausgeführt werden.
Frage 3
Was ist der Unterschied zwischen dem Blockieren und dem Sperren eines Threads? Wählen Sie alle zutreffenden Aussagen aus.
- Wenn die Ausführung blockiert ist, kann auf dem blockierten Thread keine andere Arbeit ausgeführt werden.
- Wenn die Ausführung angehalten wird, kann der Thread andere Aufgaben ausführen, während er auf den Abschluss der ausgelagerten Arbeit wartet.
- Das Anhalten ist effizienter, da Threads nicht warten und nichts tun müssen.
- Unabhängig davon, ob die Ausführung blockiert oder ausgesetzt ist, wird weiterhin auf das Ergebnis der Coroutine gewartet, bevor fortgefahren wird.
Nächste Lektion:
Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Android Kotlin Fundamentals-Codelabs.