Grundlagen von Android und Kotlin 05.2: LiveData und LiveData-Beobachter

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

Im vorherigen Codelab haben Sie in der App „GuessTheWord“ ein ViewModel verwendet, damit die Daten der App Änderungen an der Gerätekonfiguration überstehen. In diesem Codelab erfahren Sie, wie Sie LiveData in die Daten der ViewModel-Klassen einbinden. Mit LiveData, einer der Android Architecture Components, können Sie Datenobjekte erstellen, die Ansichten benachrichtigen, wenn sich die zugrunde liegende Datenbank ändert.

Um die Klasse LiveData zu verwenden, richten Sie „Beobachter“ (z. B. Aktivitäten oder Fragmente) ein, die Änderungen an den Daten der App beobachten. LiveData ist lebenszyklusbezogen. Daher werden nur App-Komponenten-Beobachter aktualisiert, die sich in einem aktiven Lebenszyklusstatus befinden.

Was Sie bereits wissen sollten

  • So erstellen Sie grundlegende Android-Apps in Kotlin.
  • So navigieren Sie zwischen den Zielen Ihrer App.
  • Aktivitäts- und Fragmentlebenszyklus.
  • ViewModel-Objekte in Ihrer App verwenden
  • ViewModel-Objekte mit der ViewModelProvider.Factory-Schnittstelle erstellen

Lerninhalte

  • Was LiveData-Objekte nützlich macht.
  • So fügen Sie LiveData zu den in einer ViewModel gespeicherten Daten hinzu.
  • Wann und wie Sie MutableLiveData verwenden.
  • Beobachtermethoden hinzufügen, um Änderungen in LiveData. zu beobachten
  • LiveData mit einer Backing Property kapseln
  • So kommunizieren Sie zwischen einem UI-Controller und dem entsprechenden ViewModel.

Aufgaben

  • Verwende LiveData für das Wort und die Punktzahl in der GuessTheWord App.
  • Fügen Sie Beobachter hinzu, die bemerken, wenn sich das Wort oder die Punktzahl ändert.
  • Aktualisieren Sie die Textansichten, in denen geänderte Werte angezeigt werden.
  • Verwende das LiveData-Beobachtermuster, um ein Ereignis für das Ende eines Spiels hinzuzufügen.
  • Implementieren Sie die Schaltfläche Nochmal spielen.

In den Codelabs zu Lektion 5 entwickeln Sie die App „GuessTheWord“ auf Grundlage von Startcode. GuessTheWord ist ein Schattenspiel für zwei Spieler, bei dem die Spieler zusammenarbeiten, um die höchstmögliche Punktzahl zu erreichen.

Der erste Spieler sieht sich die Wörter in der App an und stellt sie nacheinander dar, ohne dem zweiten Spieler das Wort zu zeigen. Der zweite Spieler versucht, das Wort zu erraten.

Um das Spiel zu starten, öffnet der erste Spieler die App auf dem Gerät und sieht ein Wort, z. B. „Gitarre“, wie im Screenshot unten zu sehen.

Der erste Spieler stellt das Wort dar, ohne es auszusprechen.

  • Wenn der zweite Spieler das Wort richtig errät, drückt der erste Spieler auf die Schaltfläche Got It (Erraten). Dadurch wird die Anzahl um eins erhöht und das nächste Wort angezeigt.
  • Wenn der zweite Spieler das Wort nicht erraten kann, drückt der erste Spieler die Schaltfläche Überspringen. Dadurch wird die Anzahl um eins verringert und zum nächsten Wort gesprungen.
  • Drücken Sie die Schaltfläche Spiel beenden, um das Spiel zu beenden. (Diese Funktion ist nicht im Startercode für das erste Codelab der Reihe enthalten.)

In diesem Codelab verbessern Sie die App „GuessTheWord“, indem Sie ein Ereignis hinzufügen, um das Spiel zu beenden, wenn der Nutzer alle Wörter in der App durchlaufen hat. Außerdem fügen Sie dem Score-Fragment die Schaltfläche Play Again (Nochmal spielen) hinzu, damit der Nutzer das Spiel noch einmal spielen kann.

Titelbildschirm

Spielbildschirm

Bewertungsbildschirm

In dieser Aufgabe suchen Sie den Startcode für dieses Codelab und führen ihn aus. Sie können die GuessTheWord-App, die Sie im vorherigen Codelab erstellt haben, als Startcode verwenden oder eine Starter-App herunterladen.

  1. (Optional) Wenn Sie den Code aus dem vorherigen Codelab nicht verwenden, laden Sie den Startcode für dieses Codelab herunter. Entpacken Sie den Code und öffnen Sie das Projekt in Android Studio.
  2. Führen Sie die App aus und spielen Sie das Spiel.
  3. Die Schaltfläche Überspringen zeigt das nächste Wort an und verringert die Punktzahl um eins. Die Schaltfläche Verstanden zeigt das nächste Wort an und erhöht die Punktzahl um eins. Mit der Schaltfläche Spiel beenden wird das Spiel beendet.

LiveData ist eine beobachtbare Datenhalterklasse, die den Lebenszyklus berücksichtigt. Sie können beispielsweise ein LiveData um die aktuelle Punktzahl in der App „GuessTheWord“ legen. In diesem Codelab erfahren Sie mehr über verschiedene Eigenschaften von LiveData:

  • LiveData ist beobachtbar. Das bedeutet, dass ein Beobachter benachrichtigt wird, wenn sich die Daten des LiveData-Objekts ändern.
  • LiveData enthält Daten; LiveData ist ein Wrapper, der mit beliebigen Daten verwendet werden kann.
  • LiveData ist lebenszyklusbezogen. Das bedeutet, dass nur Beobachter aktualisiert werden, die sich in einem aktiven Lebenszyklusstatus wie STARTED oder RESUMED befinden.

In dieser Aufgabe erfahren Sie, wie Sie einen beliebigen Datentyp in LiveData-Objekte einfügen, indem Sie die aktuellen Punkt- und Wortdaten in GameViewModel in LiveData konvertieren. In einer späteren Aufgabe fügen Sie diesen LiveData-Objekten einen Observer hinzu und erfahren, wie Sie die LiveData beobachten.

Schritt 1: Den Score und das Wort ändern, um LiveData zu verwenden

  1. Öffnen Sie im Paket screens/game die Datei GameViewModel.
  2. Ändern Sie den Typ der Variablen score und word in MutableLiveData.

    MutableLiveData ist eine LiveData, deren Wert geändert werden kann. MutableLiveData ist eine generische Klasse. Sie müssen also den Datentyp angeben, den sie enthält.
// The current word
val word = MutableLiveData<String>()
// The current score
val score = MutableLiveData<Int>()
  1. Initialisieren Sie in GameViewModel im Block init die Variablen score und word. Wenn Sie den Wert einer LiveData-Variablen ändern möchten, verwenden Sie die Methode setValue() für die Variable. In Kotlin können Sie setValue() mit der Property value aufrufen.
init {

   word.value = ""
   score.value = 0
  ...
}

Schritt 2: LiveData-Objektreferenz aktualisieren

Die Variablen score und word sind jetzt vom Typ LiveData. In diesem Schritt ändern Sie die Verweise auf diese Variablen mithilfe der Eigenschaft value.

  1. Ändern Sie in GameViewModel in der Methode onSkip() score in score.value. Beachten Sie den Fehler, dass score möglicherweise null ist. Sie beheben diesen Fehler als Nächstes.
  2. Fügen Sie in onSkip() einen null-Check zu score.value hinzu, um den Fehler zu beheben. Rufen Sie dann die Funktion minus() für score auf, um die Subtraktion mit null-Sicherheit auszuführen.
fun onSkip() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.minus(1)
   }
   nextWord()
}
  1. Aktualisieren Sie die Methode onCorrect() auf dieselbe Weise: Fügen Sie der Variablen score eine null-Prüfung hinzu und verwenden Sie die Funktion plus().
fun onCorrect() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.plus(1)
   }
   nextWord()
}
  1. Ändern Sie in GameViewModel in der Methode nextWord() den Verweis word in word.value.
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       word.value = wordList.removeAt(0)
   }
}
  1. Ändern Sie in GameFragment in der Methode updateWordText() den Verweis auf viewModel.word in viewModel.word.value..
/** Methods for updating the UI **/
private fun updateWordText() {
   binding.wordText.text = viewModel.word.value
}
  1. Ändern Sie in GameFragment in der Methode updateScoreText() den Verweis auf viewModel.score in viewModel.score.value..
private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.value.toString()
}
  1. Ändern Sie in GameFragment in der Methode gameFinished() den Verweis auf viewModel.score in viewModel.score.value. Fügen Sie den erforderlichen null-Sicherheitscheck hinzu.
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score.value?:0
   NavHostFragment.findNavController(this).navigate(action)
}
  1. Achten Sie darauf, dass Ihr Code keine Fehler enthält. Kompilieren und führen Sie Ihre App aus. Die Funktionalität der App sollte dieselbe sein wie zuvor.

Diese Aufgabe hängt eng mit der vorherigen Aufgabe zusammen, in der Sie die Punktzahl- und Wortdaten in LiveData-Objekte konvertiert haben. In dieser Aufgabe hängen Sie Observer-Objekte an diese LiveData-Objekte an.

  1. Hängen Sie in GameFragment, in der Methode onCreateView() ein Observer-Objekt an das LiveData-Objekt für die aktuelle Punktzahl, viewModel.score, an. Verwenden Sie die Methode observe() und fügen Sie den Code nach der Initialisierung von viewModel ein. Verwenden Sie einen Lambda-Ausdruck, um den Code zu vereinfachen. Ein Lambda-Ausdruck ist eine anonyme Funktion, die nicht deklariert, sondern sofort als Ausdruck übergeben wird.
viewModel.score.observe(this, Observer { newScore ->
})

Lösen Sie den Verweis auf Observer auf. Klicken Sie dazu auf Observer, drücken Sie Alt+Enter (Option+Enter auf einem Mac) und importieren Sie androidx.lifecycle.Observer.

  1. Der gerade erstellte Observer empfängt ein Ereignis, wenn sich die Daten des beobachteten LiveData-Objekts ändern. Aktualisieren Sie im Observer den Wert für TextView mit dem neuen Wert.
/** Setting up LiveData observation relationship **/
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. Hängen Sie ein Observer-Objekt an das aktuelle LiveData-Objekt an. Gehen Sie dabei genauso vor wie beim Anhängen eines Observer-Objekts an den aktuellen Score.
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
   binding.wordText.text = newWord
})

Wenn sich der Wert von score oder word ändert, wird der auf dem Bildschirm angezeigte Wert von score oder word jetzt automatisch aktualisiert.

  1. Löschen Sie in GameFragment die Methoden updateWordText() und updateScoreText() sowie alle Verweise darauf. Sie werden nicht mehr benötigt, da die Textansichten durch die LiveData-Beobachtermethoden aktualisiert werden.
  2. Führen Sie Ihre App aus. Ihre Spiele-App sollte genau wie zuvor funktionieren, verwendet jetzt aber LiveData und LiveData-Beobachter.

Kapselung ist eine Möglichkeit, den direkten Zugriff auf einige Felder eines Objekts einzuschränken. Wenn Sie ein Objekt kapseln, stellen Sie eine Reihe öffentlicher Methoden bereit, mit denen die privaten internen Felder geändert werden können. Durch die Kapselung steuern Sie, wie andere Klassen diese internen Felder bearbeiten.

In Ihrem aktuellen Code kann jede externe Klasse die Variablen score und word mit der Eigenschaft value ändern, z. B. mit viewModel.score.value. In der App, die Sie in diesem Codelab entwickeln, spielt das möglicherweise keine Rolle. In einer Produktions-App sollten Sie jedoch die Daten in den ViewModel-Objekten kontrollieren.

Nur die ViewModel sollte die Daten in Ihrer App bearbeiten. UI-Controller müssen die Daten jedoch lesen können, daher können die Datenfelder nicht vollständig privat sein. Zum Kapseln der Daten Ihrer App verwenden Sie sowohl MutableLiveData- als auch LiveData-Objekte.

MutableLiveData im Vergleich zu LiveData:

  • Daten in einem MutableLiveData-Objekt können geändert werden, wie der Name schon sagt. Innerhalb von ViewModel sollten die Daten bearbeitbar sein. Daher wird MutableLiveData verwendet.
  • Daten in einem LiveData-Objekt können gelesen, aber nicht geändert werden. Außerhalb von ViewModel sollten Daten lesbar, aber nicht bearbeitbar sein. Daher sollten die Daten als LiveData verfügbar gemacht werden.

Für diese Strategie verwenden Sie eine Backing Property in Kotlin. Mit einer Backing Property können Sie etwas anderes als das genaue Objekt aus einem Getter zurückgeben. In dieser Aufgabe implementieren Sie eine Sicherungseigenschaft für die Objekte score und word in der App „GuessTheWord“.

Dem Ergebnis und dem Wort eine unterstützende Property hinzufügen

  1. Machen Sie in GameViewModel das aktuelle score-Objekt zu private.
  2. Wenn Sie die in den zugrunde liegenden Attributen verwendete Namenskonvention verwenden möchten, ändern Sie score in _score. Die Property _score ist jetzt die veränderbare Version der Spielpunktzahl, die intern verwendet werden soll.
  3. Erstellen Sie eine öffentliche Version des Typs LiveData mit dem Namen score.
// The current score
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
  1. Es wird ein Initialisierungsfehler angezeigt. Dieser Fehler tritt auf, weil score in GameFragment ein LiveData-Verweis ist und score nicht mehr auf seinen Setter zugreifen kann. Weitere Informationen zu Gettern und Settern in Kotlin finden Sie unter Getter und Setter.

    Um den Fehler zu beheben, überschreiben Sie die Methode get() für das score-Objekt in GameViewModel und geben Sie die Sicherungseigenschaft _score zurück.
val score: LiveData<Int>
   get() = _score
  1. Ändern Sie im GameViewModel die Verweise von score in die interne veränderliche Version _score.
init {
   ...
   _score.value = 0
   ...
}

...
fun onSkip() {
   if (!wordList.isEmpty()) {
       _score.value = (score.value)?.minus(1)
   }
  ...
}

fun onCorrect() {
   if (!wordList.isEmpty()) {
       _score.value = (score.value)?.plus(1)
   }
   ...
}
  1. Benennen Sie das word-Objekt in _word um und fügen Sie eine Backing-Property dafür hinzu, wie Sie es für das score-Objekt getan haben.
// The current word
private val _word = MutableLiveData<String>()
val word: LiveData<String>
   get() = _word
...
init {
   _word.value = ""
   ...
}
...
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       _word.value = wordList.removeAt(0)
   }
}

Gut gemacht! Sie haben die LiveData-Objekte word und score gekapselt.

In Ihrer aktuellen App wird der Nutzer zum Bildschirm mit dem Ergebnis weitergeleitet, wenn er auf die Schaltfläche Spiel beenden tippt. Außerdem soll die App zum Bildschirm mit der Punktzahl wechseln, wenn die Spieler alle Wörter durchlaufen haben. Nachdem die Spieler das letzte Wort eingegeben haben, soll das Spiel automatisch beendet werden, damit der Nutzer nicht auf die Schaltfläche tippen muss.

Um diese Funktion zu implementieren, muss ein Ereignis ausgelöst und an das Fragment von ViewModel gesendet werden, wenn alle Wörter angezeigt wurden. Dazu verwenden Sie das LiveData-Beobachtermuster, um ein Ereignis für das Ende des Spiels zu modellieren.

Das Observer-Muster

Das Beobachtermuster ist ein Software-Designmuster. Es wird die Kommunikation zwischen Objekten festgelegt: einem Observable (dem „Subjekt“ der Beobachtung) und Observers. Ein Observable ist ein Objekt, das Beobachter über Änderungen in seinem Status informiert.

Im Fall von LiveData in dieser App ist das Observable (Subjekt) das LiveData-Objekt und die Beobachter sind die Methoden in den UI-Controllern, z. B. Fragmente. Eine Statusänderung erfolgt immer dann, wenn sich die in LiveData enthaltenen Daten ändern. Die LiveData-Klassen sind entscheidend für die Kommunikation vom ViewModel zum Fragment.

Schritt 1: LiveData verwenden, um ein Ereignis für das Ende eines Spiels zu erkennen

In dieser Aufgabe verwenden Sie das LiveData-Beobachtermuster, um ein Ereignis zu modellieren, das ausgelöst wird, wenn ein Spiel beendet ist.

  1. Erstellen Sie in GameViewModel ein Boolean-MutableLiveData-Objekt mit dem Namen _eventGameFinish. Dieses Objekt enthält das Ereignis „Spiel beendet“.
  2. Erstellen und initialisieren Sie nach der Initialisierung des _eventGameFinish-Objekts eine Sicherungsproperty namens eventGameFinish.
// Event which triggers the end of the game
private val _eventGameFinish = MutableLiveData<Boolean>()
val eventGameFinish: LiveData<Boolean>
   get() = _eventGameFinish
  1. Fügen Sie in GameViewModel eine onGameFinish()-Methode hinzu. Legen Sie in der Methode das Ereignis „Spiel beendet“ (eventGameFinish) auf true fest.
/** Method for the game completed event **/
fun onGameFinish() {
   _eventGameFinish.value = true
}
  1. Beende das Spiel in GameViewModel in der Methode nextWord(), wenn die Wortliste leer ist.
private fun nextWord() {
   if (wordList.isEmpty()) {
       onGameFinish()
   } else {
       //Select and remove a _word from the list
       _word.value = wordList.removeAt(0)
   }
}
  1. Hängen Sie in GameFragment innerhalb von onCreateView() nach der Initialisierung von viewModel einen Observer an eventGameFinish an. Verwenden Sie die Methode observe(). Rufen Sie in der Lambda-Funktion die Methode gameFinished() auf.
// Observer for the Game finished event
viewModel.eventGameFinish.observe(this, Observer<Boolean> { hasFinished ->
   if (hasFinished) gameFinished()
})
  1. Führen Sie Ihre App aus, spielen Sie das Spiel und gehen Sie alle Wörter durch. Die App wechselt automatisch zum Bildschirm mit dem Ergebnis, anstatt im Spiel-Fragment zu bleiben, bis Sie auf Spiel beenden tippen.

    Wenn die Wortliste leer ist, wird eventGameFinish festgelegt, die zugehörige Beobachtermethode im Game-Fragment wird aufgerufen und die App wechselt zum Bildschirm-Fragment.
  2. Der von Ihnen hinzugefügte Code hat ein Problem mit dem Lebenszyklus verursacht. Um das Problem zu verstehen, kommentieren Sie in der Klasse GameFragment den Navigationscode in der Methode gameFinished() aus. Achten Sie darauf, die Toast-Nachricht in der Methode beizubehalten.
private fun gameFinished() {
       Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
//        val action = GameFragmentDirections.actionGameToScore()
//        action.score = viewModel.score.value?:0
//        NavHostFragment.findNavController(this).navigate(action)
   }
  1. Führen Sie Ihre App aus, spielen Sie das Spiel und gehen Sie alle Wörter durch. Unten auf dem Spielbildschirm wird kurz die Toast-Meldung „Das Spiel ist gerade beendet“ angezeigt. Das ist das erwartete Verhalten.

Drehen Sie das Gerät oder den Emulator. Der Hinweis wird wieder angezeigt. Drehen Sie das Gerät noch einige Male. Wahrscheinlich wird der Toast jedes Mal angezeigt. Das ist ein Fehler, da der Hinweis nur einmal angezeigt werden sollte, wenn das Spiel beendet ist. Der Toast sollte nicht jedes Mal angezeigt werden, wenn das Fragment neu erstellt wird. Sie beheben dieses Problem in der nächsten Aufgabe.

Schritt 2: Ereignis „Spiel beendet“ zurücksetzen

Normalerweise werden Aktualisierungen nur dann an die Beobachter gesendet, wenn sich Daten ändern.LiveData Eine Ausnahme von diesem Verhalten besteht darin, dass Beobachter auch dann Updates erhalten, wenn sich der Beobachter von einem inaktiven in einen aktiven Status ändert.

Aus diesem Grund wird der Toast „Spiel beendet“ in Ihrer App wiederholt ausgelöst. Wenn das Game-Fragment nach einer Bildschirmdrehung neu erstellt wird, wechselt es von einem inaktiven in einen aktiven Zustand. Der Observer im Fragment wird wieder mit dem vorhandenen ViewModel verbunden und empfängt die aktuellen Daten. Die Methode gameFinished() wird noch einmal ausgelöst und der Toast wird angezeigt.

In dieser Aufgabe beheben Sie dieses Problem und lassen den Toast nur einmal anzeigen, indem Sie das Flag eventGameFinish in GameViewModel zurücksetzen.

  1. Fügen Sie in GameViewModel eine onGameFinishComplete()-Methode zum Zurücksetzen des Ereignisses „Spiel beendet“ (_eventGameFinish) hinzu.
/** Method for the game completed event **/

fun onGameFinishComplete() {
   _eventGameFinish.value = false
}
  1. Rufen Sie in GameFragment am Ende von gameFinished() onGameFinishComplete() für das viewModel-Objekt auf. Lassen Sie den Navigationscode in gameFinished() vorerst auskommentiert.
private fun gameFinished() {
   ...
   viewModel.onGameFinishComplete()
}
  1. Führen Sie die App aus und spielen Sie das Spiel. Gehen Sie alle Wörter durch und ändern Sie dann die Bildschirmausrichtung des Geräts. Der Hinweis wird nur einmal angezeigt.
  2. Entfernen Sie in GameFragment innerhalb der Methode gameFinished() die Auskommentierung des Navigationscodes.

    Wählen Sie in Android Studio die auskommentierten Zeilen aus und drücken Sie Control+/ (Command+/ auf einem Mac), um die Auskommentierung zu entfernen.
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score.value?:0
   findNavController(this).navigate(action)
   viewModel.onGameFinishComplete()
}

Wenn Sie von Android Studio dazu aufgefordert werden, importieren Sie androidx.navigation.fragment.NavHostFragment.findNavController.

  1. Führen Sie die App aus und spielen Sie das Spiel. Die App muss nach dem Durchgehen aller Wörter automatisch zum Bildschirm mit dem Endergebnis wechseln.

Gut gemacht! Ihre App verwendet LiveData, um ein Ereignis zum Spielende auszulösen und dem Spiel-Fragment mitzuteilen, dass die Wortliste leer ist.GameViewModel Das Spielfragment wechselt dann zum Ergebnisfragment.

In dieser Aufgabe ändern Sie den Wert in ein LiveData-Objekt in ScoreViewModel und hängen einen Observer daran an. Diese Aufgabe ähnelt der, die Sie beim Hinzufügen von LiveData zur GameViewModel ausgeführt haben.

Sie nehmen diese Änderungen an ScoreViewModel vor, damit alle Daten in Ihrer App LiveData verwenden.

  1. Ändern Sie in ScoreViewModel den Variablentyp score in MutableLiveData. Benennen Sie sie gemäß der Konvention in _score um und fügen Sie ein Backing-Attribut hinzu.
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
   get() = _score
  1. Initialisieren Sie _score in ScoreViewModel innerhalb des init-Blocks. Sie können das Log nach Belieben aus dem init-Block entfernen oder dort belassen.
init {
   _score.value = finalScore
}
  1. Hängen Sie in ScoreFragment innerhalb von onCreateView() nach der Initialisierung von viewModel einen Observer für das Score-Objekt LiveData an. Legen Sie im Lambda-Ausdruck den Punktwert auf die Textansicht für die Punktzahl fest. Entfernen Sie den Code, mit dem der Textansicht der Punktwert aus ViewModel direkt zugewiesen wird.

Code zum Hinzufügen:

// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})

Zu entfernender Code:

binding.scoreText.text = viewModel.score.toString()

Importieren Sie androidx.lifecycle.Observer, wenn Sie von Android Studio dazu aufgefordert werden.

  1. Führen Sie die App aus und spielen Sie das Spiel. Die App sollte wie zuvor funktionieren, verwendet jetzt aber LiveData und einen Observer, um den Score zu aktualisieren.

In dieser Aufgabe fügen Sie dem Ergebnisbildschirm die Schaltfläche Nochmal spielen hinzu und implementieren den Klick-Listener mit einem LiveData-Ereignis. Die Schaltfläche löst ein Ereignis aus, um vom Ergebnisbildschirm zum Spielbildschirm zu wechseln.

Der Startcode für die App enthält die Schaltfläche Nochmal spielen, die jedoch ausgeblendet ist.

  1. Ändern Sie in res/layout/score_fragment.xml für die Schaltfläche play_again_button den Wert des Attributs visibility in visible.
<Button
   android:id="@+id/play_again_button"
...
   android:visibility="visible"
 />
  1. Fügen Sie in ScoreViewModel ein LiveData-Objekt ein, das einen Boolean namens _eventPlayAgain enthält. Mit diesem Objekt wird das LiveData-Ereignis gespeichert, um vom Ergebnisbildschirm zum Spielbildschirm zu wechseln.
private val _eventPlayAgain = MutableLiveData<Boolean>()
val eventPlayAgain: LiveData<Boolean>
   get() = _eventPlayAgain
  1. Definieren Sie in ScoreViewModel Methoden zum Festlegen und Zurücksetzen des Ereignisses, _eventPlayAgain.
fun onPlayAgain() {
   _eventPlayAgain.value = true
}
fun onPlayAgainComplete() {
   _eventPlayAgain.value = false
}
  1. Fügen Sie in ScoreFragment einen Observer für eventPlayAgain hinzu. Setzen Sie den Code ans Ende von onCreateView(), vor die return-Anweisung. Gehen Sie im Lambda-Ausdruck zurück zum Spielbildschirm und setzen Sie eventPlayAgain zurück.
// Navigates back to game when button is pressed
viewModel.eventPlayAgain.observe(this, Observer { playAgain ->
   if (playAgain) {
      findNavController().navigate(ScoreFragmentDirections.actionRestart())
       viewModel.onPlayAgainComplete()
   }
})

Importieren Sie androidx.navigation.fragment.findNavController, wenn Sie von Android Studio dazu aufgefordert werden.

  1. Fügen Sie in ScoreFragment innerhalb von onCreateView() einen Klick-Listener für die Schaltfläche PlayAgain hinzu und rufen Sie viewModel.onPlayAgain() auf.
binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. Führen Sie die App aus und spielen Sie das Spiel. Wenn das Spiel beendet ist, wird auf dem Bildschirm mit dem Ergebnis die Endpunktzahl und die Schaltfläche Nochmal spielen angezeigt. Tippen Sie auf die Schaltfläche PlayAgain (Nochmal spielen). Die App leitet Sie dann zum Spielbildschirm weiter, damit Sie das Spiel noch einmal spielen können.

Gute Arbeit! Sie haben die Architektur Ihrer App so geändert, dass LiveData-Objekte im ViewModel verwendet werden, und Sie haben Beobachter an die LiveData-Objekte angehängt. LiveData benachrichtigt Beobachterobjekte, wenn sich der von LiveData gehaltene Wert ändert.

Android Studio-Projekt: GuessTheWord

LiveData

  • LiveData ist eine beobachtbare Datenhalterklasse, die den Lebenszyklus berücksichtigt und zu den Android-Architekturkomponenten gehört.
  • Mit LiveData können Sie dafür sorgen, dass die Benutzeroberfläche automatisch aktualisiert wird, wenn sich die Daten ändern.
  • LiveData ist beobachtbar. Das bedeutet, dass ein Beobachter wie eine Aktivität oder ein Fragment benachrichtigt werden kann, wenn sich die Daten im LiveData-Objekt ändern.
  • LiveData enthält Daten. Es ist ein Wrapper, der mit beliebigen Daten verwendet werden kann.
  • LiveData ist lebenszyklusbezogen. Das bedeutet, dass nur Beobachter aktualisiert werden, die sich in einem aktiven Lebenszyklusstatus wie STARTED oder RESUMED befinden.

LiveData hinzufügen

  • Ändern Sie den Typ der Datenvariablen in ViewModel in LiveData oder MutableLiveData.

MutableLiveData ist ein LiveData-Objekt, dessen Wert geändert werden kann. MutableLiveData ist eine generische Klasse. Sie müssen also den Datentyp angeben, den sie enthält.

  • Wenn Sie den Wert der Daten ändern möchten, die von LiveData enthalten sind, verwenden Sie die Methode setValue() für die Variable LiveData.

LiveData kapseln

  • Die LiveData in ViewModel sollte bearbeitbar sein. Außerhalb von ViewModel sollte LiveData lesbar sein. Dies kann mit einem Backing Property in Kotlin implementiert werden.
  • Mit einer Kotlin-Backing-Property können Sie in einem Getter etwas anderes als das genaue Objekt zurückgeben.
  • Um LiveData zu kapseln, verwenden Sie private MutableLiveData innerhalb von ViewModel und geben Sie außerhalb von ViewModel eine LiveData-Backing-Property zurück.

Observable LiveData

  • LiveData folgt einem Observer-Muster. Das „beobachtbare“ Objekt ist das LiveData-Objekt und die Beobachter sind die Methoden in den UI-Controllern, z. B. Fragmente. Immer wenn sich die in LiveData enthaltenen Daten ändern, werden die Observer-Methoden in den UI-Controllern benachrichtigt.
  • Damit das LiveData beobachtbar ist, hängen Sie mit der Methode observe() ein Beobachterobjekt an die LiveData-Referenz in den Beobachtern (z. B. Aktivitäten und Fragmenten) an.
  • Dieses LiveData-Beobachtermuster kann für die Kommunikation vom ViewModel zu den UI-Controllern verwendet werden.

Udacity-Kurs:

Android-Entwicklerdokumentation:

Sonstiges:

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

Wie kapseln Sie die LiveData, die in einem ViewModel gespeichert sind, sodass externe Objekte Daten lesen können, ohne sie aktualisieren zu können?

  • Ändern Sie im ViewModel-Objekt den Datentyp der Daten in private LiveData. Verwenden Sie eine Backing Property, um schreibgeschützte Daten vom Typ MutableLiveData verfügbar zu machen.
  • Ändern Sie im ViewModel-Objekt den Datentyp der Daten in private MutableLiveData. Verwenden Sie eine Backing Property, um schreibgeschützte Daten vom Typ LiveData verfügbar zu machen.
  • Ändern Sie im UI-Controller den Datentyp der Daten in private MutableLiveData. Verwenden Sie eine Backing Property, um schreibgeschützte Daten vom Typ LiveData verfügbar zu machen.
  • Ändern Sie im ViewModel-Objekt den Datentyp der Daten in LiveData. Verwenden Sie eine Backing Property, um schreibgeschützte Daten vom Typ LiveData verfügbar zu machen.

Frage 2

LiveData aktualisiert einen UI-Controller (z. B. ein Fragment), wenn sich der UI-Controller in einem der folgenden Status befindet:

  • Fortgesetzt
  • Im Hintergrund
  • Pausiert
  • Angehalten

Frage 3

Was ist im LiveData-Beobachtermuster das beobachtbare Element (was wird beobachtet)?

  • Die Beobachtermethode
  • Die Daten in einem LiveData-Objekt
  • Der UI-Controller
  • Das ViewModel-Objekt

Nächste Lektion: 5.3: Data Binding mit ViewModel und LiveData

Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Android Kotlin Fundamentals-Codelabs.