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.
Titelbildschirm |
Spielbildschirm |
Bewertungsbildschirm |
Einführung
In diesem Codelab lernen Sie eine der Android-Architekturkomponenten kennen: ViewModel.
- Mit der Klasse
ViewModelkönnen Sie UI-bezogene Daten auf lebenszyklusbewusste Weise speichern und verwalten. Mit der KlasseViewModelkönnen Daten Änderungen der Gerätekonfiguration wie Bildschirmdrehungen und Änderungen der Tastaturverfügbarkeit überdauern. - Mit der Klasse
ViewModelFactoryinstanziieren und geben Sie dasViewModel-Objekt zurück, das Konfigurationsänderungen übersteht.
Was Sie bereits wissen sollten
- So erstellen Sie grundlegende Android-Apps in Kotlin.
- So verwenden Sie den Navigationsgraphen, um die Navigation in Ihrer App zu implementieren.
- Code hinzufügen, um zwischen den Zielen Ihrer App zu wechseln und Daten zwischen Navigationszielen zu übergeben
- Funktionsweise des Aktivitäts- und Fragmentlebenszyklus.
- Informationen dazu, wie Sie einer App Logging-Informationen hinzufügen und Logs mit Logcat in Android Studio lesen.
Lerninhalte
- So verwenden Sie die empfohlene App-Architektur für Android.
- So verwenden Sie die Klassen
Lifecycle,ViewModelundViewModelFactoryin Ihrer App. - UI-Daten bei Änderungen der Gerätekonfiguration beibehalten
- Was das Fabrikmethoden-Entwurfsmuster ist und wie es verwendet wird.
- So erstellen Sie ein
ViewModel-Objekt mit der SchnittstelleViewModelProvider.Factory.
Aufgaben
- Fügen Sie der App ein
ViewModelhinzu, um die Daten der App zu speichern, damit sie Konfigurationsänderungen überstehen. - Verwenden Sie
ViewModelFactoryund das Factory-Method-Entwurfsmuster, um einViewModel-Objekt mit Konstruktorparametern zu instanziieren.
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 dieser Aufgabe laden Sie die Starter-App herunter, führen sie aus und sehen sich den Code an.
Schritt 1: Vorbereitung
- Laden Sie den GuessTheWord-Startcode herunter und öffnen Sie das Projekt in Android Studio.
- Führen Sie die App auf einem Android-Gerät oder in einem Emulator aus.
- Tippe auf die Tasten. 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. Die Schaltfläche Spiel beenden ist nicht implementiert. Wenn Sie darauf tippen, passiert also nichts.
Schritt 2: Code durchgehen
- Sehen Sie sich den Code in Android Studio an, um ein Gefühl dafür zu bekommen, wie die App funktioniert.
- Sehen Sie sich unbedingt die unten beschriebenen Dateien an, die besonders wichtig sind.
MainActivity.kt
Diese Datei enthält nur Standardcode, der durch die Vorlage generiert wurde.
res/layout/main_activity.xml
Diese Datei enthält das Hauptlayout der App. Im NavHostFragment werden die anderen Fragmente gehostet, während der Nutzer durch die App navigiert.
UI-Fragmente
Der Startercode enthält drei Fragmente in drei verschiedenen Paketen unter dem Paket com.example.android.guesstheword.screens:
title/TitleFragmentfür den Titelbildschirmgame/GameFragmentfür den Spielbildschirmscore/ScoreFragmentfür den Bildschirm mit der Punktzahl
screens/title/TitleFragment.kt
Das Titelfragment ist der erste Bildschirm, der beim Starten der App angezeigt wird. Für die Schaltfläche Play (Spielen) wird ein Click-Handler festgelegt, um zum Spielbildschirm zu navigieren.
screens/game/GameFragment.kt
Das ist das Hauptfragment, in dem der Großteil der Spielhandlung stattfindet:
- Variablen werden für das aktuelle Wort und die aktuelle Punktzahl definiert.
- Die
wordList, die in der MethoderesetList()definiert ist, ist eine Beispielliste mit Wörtern, die im Spiel verwendet werden sollen. - Die
onSkip()-Methode ist der Click-Handler für die Schaltfläche Überspringen. Der Wert wird um 1 verringert und das nächste Wort wird mit der MethodenextWord()angezeigt. - Die Methode
onCorrect()ist der Klick-Handler für die Schaltfläche Verstanden. Diese Methode wird ähnlich wie die MethodeonSkip()implementiert. Der einzige Unterschied besteht darin, dass bei dieser Methode 1 zum Ergebnis addiert wird, anstatt es zu subtrahieren.
screens/score/ScoreFragment.kt
ScoreFragment ist der letzte Bildschirm im Spiel und zeigt die Endpunktzahl des Spielers an. In diesem Codelab fügen Sie die Implementierung hinzu, um diesen Bildschirm und die endgültige Punktzahl anzuzeigen.
res/navigation/main_navigation.xml
Das Navigationsdiagramm zeigt, wie die Fragmente über die Navigation miteinander verbunden sind:
- Über das Titelfragment kann der Nutzer zum Spielfragment navigieren.
- Vom Spiel-Fragment aus kann der Nutzer zum Ergebnis-Fragment navigieren.
- Über das Score-Fragment kann der Nutzer zum Spiel-Fragment zurückkehren.
In dieser Aufgabe suchen Sie nach Problemen in der Starter-App „GuessTheWord“.
- Führen Sie den Startercode aus und spielen Sie das Spiel einige Wörter lang. Tippen Sie nach jedem Wort auf Überspringen oder Verstanden.
- Auf dem Spielbildschirm werden jetzt ein Wort und der aktuelle Punktestand angezeigt. Ändern Sie die Bildschirmausrichtung, indem Sie das Gerät oder den Emulator drehen. Beachten Sie, dass die aktuelle Punktzahl verloren geht.
- Lass das Spiel noch ein paar Wörter durchlaufen. Wenn der Spielbildschirm mit einem Ergebnis angezeigt wird, schließen Sie die App und öffnen Sie sie wieder. Das Spiel wird von Anfang an neu gestartet, da der App-Status nicht gespeichert wird.
- Spielen Sie das Spiel einige Wörter lang und tippen Sie dann auf die Schaltfläche Spiel beenden. Es passiert nichts.
Probleme in der App:
- Die Starter-App speichert und stellt den App-Status bei Konfigurationsänderungen nicht wieder her, z. B. wenn sich die Geräteausrichtung ändert oder die App beendet und neu gestartet wird.
Sie können dieses Problem mit demonSaveInstanceState()-Callback beheben. Wenn Sie dieonSaveInstanceState()-Methode verwenden, müssen Sie jedoch zusätzlichen Code schreiben, um den Status in einem Bundle zu speichern und die Logik zum Abrufen dieses Status zu implementieren. Außerdem ist die Menge der Daten, die gespeichert werden können, minimal. - Der Spielbildschirm wird nicht zum Ergebnisbildschirm weitergeleitet, wenn der Nutzer auf die Schaltfläche Spiel beenden tippt.
Sie können diese Probleme mit den App-Architekturkomponenten beheben, die Sie in diesem Codelab kennenlernen.
Anwendungsarchitektur
Die App-Architektur ist eine Methode zum Entwerfen der Klassen Ihrer Apps und der Beziehungen zwischen ihnen, sodass der Code organisiert ist, in bestimmten Szenarien eine gute Leistung erbringt und einfach zu verwenden ist. In dieser Reihe von vier Codelabs folgen die Verbesserungen, die Sie an der App „GuessTheWord“ vornehmen, den Richtlinien für die Android-App-Architektur. Außerdem verwenden Sie Android-Architekturkomponenten. Die Android-App-Architektur ähnelt dem MVVM-Architekturmuster (Model-View-ViewModel).
Die GuessTheWord-App folgt dem Designprinzip der Trennung von Belangen und ist in Klassen unterteilt, wobei jede Klasse einen separaten Belang abdeckt. In diesem ersten Codelab der Lektion arbeiten Sie mit einem UI-Controller, einem ViewModel und einem ViewModelFactory.
UI-Controller
Ein UI-Controller ist eine UI-basierte Klasse wie Activity oder Fragment. Ein UI-Controller sollte nur Logik enthalten, die UI- und Betriebssysteminteraktionen verarbeitet, z. B. das Anzeigen von Ansichten und das Erfassen von Nutzereingaben. Die Entscheidungslogik, z. B. die Logik, die den anzuzeigenden Text bestimmt, sollte nicht im UI-Controller enthalten sein.
Im Startcode für „GuessTheWord“ sind die UI-Controller die drei Fragmente GameFragment, ScoreFragment, und TitleFragment. Gemäß dem Designprinzip „Trennung von Belangen“ ist die GameFragment nur dafür verantwortlich, Spielelemente auf dem Bildschirm darzustellen und zu erkennen, wann der Nutzer auf die Schaltflächen tippt. Wenn der Nutzer auf eine Schaltfläche tippt, werden diese Informationen an GameViewModel übergeben.
ViewModel
Ein ViewModel enthält Daten, die in einem Fragment oder einer Aktivität angezeigt werden sollen, die mit dem ViewModel verknüpft ist. Ein ViewModel kann einfache Berechnungen und Transformationen für Daten ausführen, um die Daten für die Anzeige durch den UI-Controller vorzubereiten. In dieser Architektur trifft die ViewModel die Entscheidungen.
Die GameViewModel enthält Daten wie den Punktwert, die Liste der Wörter und das aktuelle Wort, da diese Daten auf dem Bildschirm angezeigt werden sollen. Die GameViewModel enthält auch die Geschäftslogik für einfache Berechnungen, um den aktuellen Zustand der Daten zu ermitteln.
ViewModelFactory
Ein ViewModelFactory instanziiert ViewModel-Objekte mit oder ohne Konstruktorparameter.

In späteren Codelabs erfahren Sie mehr über andere Android-Architekturkomponenten, die mit UI-Controllern und ViewModel zusammenhängen.
Die Klasse ViewModel dient zum Speichern und Verwalten der UI-bezogenen Daten. In dieser App ist jedes ViewModel mit einem Fragment verknüpft.
In dieser Aufgabe fügen Sie Ihrer App das erste ViewModel hinzu, nämlich das GameViewModel für das GameFragment. Außerdem erfahren Sie, was es bedeutet, dass die ViewModel lebenszyklusbezogen ist.
Schritt 1: GameViewModel-Klasse hinzufügen
- Öffnen Sie die Datei
build.gradle(module:app). Fügen Sie im Blockdependenciesdie Gradle-Abhängigkeit fürViewModel.
hinzu. Wenn Sie die neueste Version der Bibliothek verwenden, sollte die Lösungs-App wie erwartet kompiliert werden. Wenn nicht, versuchen Sie, das Problem zu beheben, oder kehren Sie zur unten gezeigten Version zurück.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'- Erstellen Sie im Ordner des Pakets
screens/game/eine neue Kotlin-Klasse mit dem NamenGameViewModel. - Lassen Sie die Klasse
GameViewModeldie abstrakte KlasseViewModelerweitern. - Damit Sie besser nachvollziehen können, wie
ViewModelden Lebenszyklus berücksichtigt, fügen Sie eineninit-Block mit einerlog-Anweisung hinzu.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}Schritt 2: onCleared() überschreiben und Logging hinzufügen
Die ViewModel wird zerstört, wenn das zugehörige Fragment getrennt oder die Aktivität beendet wird. Kurz bevor ViewModel zerstört wird, wird der onCleared()-Callback aufgerufen, um die Ressourcen zu bereinigen.
- Überschreiben Sie in der Klasse
GameViewModeldie MethodeonCleared(). - Fügen Sie eine Log-Anweisung in
onCleared()ein, um den Lebenszyklus vonGameViewModelzu verfolgen.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}Schritt 3: GameViewModel dem Game-Fragment zuordnen
Ein ViewModel muss einem UI-Controller zugeordnet sein. Um die beiden zu verknüpfen, erstellen Sie im UI-Controller einen Verweis auf ViewModel.
In diesem Schritt erstellen Sie einen Verweis auf GameViewModel im entsprechenden UI-Controller, also GameFragment.
- Fügen Sie in der Klasse
GameFragmentein Feld vom TypGameViewModelauf der obersten Ebene als Klassenvariable hinzu.
private lateinit var viewModel: GameViewModelSchritt 4: ViewModel initialisieren
Bei Konfigurationsänderungen wie Bildschirmrotationen werden UI-Controller wie Fragmente neu erstellt. ViewModel-Instanzen bleiben jedoch erhalten. Wenn Sie die ViewModel-Instanz mit der ViewModel-Klasse erstellen, wird jedes Mal, wenn das Fragment neu erstellt wird, ein neues Objekt erstellt. Erstellen Sie die ViewModel-Instanz stattdessen mit einem ViewModelProvider.

So funktioniert ViewModelProvider:
ViewModelProvidergibt einen vorhandenenViewModelzurück, falls einer vorhanden ist, oder erstellt einen neuen, falls noch keiner vorhanden ist.- Mit
ViewModelProviderwird eineViewModel-Instanz in Verbindung mit dem angegebenen Bereich (einer Aktivität oder einem Fragment) erstellt. - Das erstellte
ViewModelwird so lange beibehalten, wie der Bereich aktiv ist. Wenn der Bereich beispielsweise ein Fragment ist, wirdViewModelbeibehalten, bis das Fragment getrennt wird.
Initialisieren Sie ViewModel mit der Methode ViewModelProviders.of(), um ein ViewModelProvider zu erstellen:
- Initialisieren Sie in der Klasse
GameFragmentdie VariableviewModel. Fügen Sie diesen Code inonCreateView()nach der Definition der Bindungsvariable ein. Verwenden Sie die MethodeViewModelProviders.of()und übergeben Sie den zugehörigenGameFragment-Kontext und dieGameViewModel-Klasse. - Fügen Sie vor der Initialisierung des
ViewModel-Objekts eine Log-Anweisung hinzu, um denViewModelProviders.of()-Methodenaufruf zu protokollieren.
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)- Führen Sie die App aus. Öffnen Sie in Android Studio den Bereich Logcat und filtern Sie nach
Game. Tippe auf deinem Gerät oder Emulator auf die Schaltfläche Wiedergabe. Der Spielbildschirm wird geöffnet.
Wie im Logcat zu sehen ist, ruft die MethodeonCreateView()der KlasseGameFragmentdie MethodeViewModelProviders.of()auf, um die KlasseGameViewModelzu erstellen. Die Logging-Anweisungen, die SieGameFragmentundGameViewModelhinzugefügt haben, werden in Logcat angezeigt.

- Aktivieren Sie die Einstellung „Automatisch drehen“ auf Ihrem Gerät oder Emulator und ändern Sie die Bildschirmausrichtung einige Male. Die
GameFragmentwird jedes Mal zerstört und neu erstellt, sodassViewModelProviders.of()jedes Mal aufgerufen wird.GameViewModelwird jedoch nur einmal erstellt und nicht bei jedem Aufruf neu erstellt oder zerstört.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- Beenden Sie das Spiel oder verlassen Sie das Spiel-Fragment. Das
GameFragmentwird gelöscht. Die zugehörigeGameViewModelwird ebenfalls zerstört und der CallbackonCleared()wird aufgerufen.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
Die ViewModel bleibt bei Konfigurationsänderungen erhalten. Sie eignet sich daher gut für Daten, die bei Konfigurationsänderungen erhalten bleiben müssen:
- Die Daten, die auf dem Bildschirm angezeigt werden sollen, und der Code zum Verarbeiten dieser Daten gehören in den
ViewModel. - Die
ViewModelsollte niemals Verweise auf Fragmente, Aktivitäten oder Ansichten enthalten, da Aktivitäten, Fragmente und Ansichten Konfigurationsänderungen nicht überstehen.

Zum Vergleich sehen Sie hier, wie die GameFragment-UI-Daten in der Starter-App behandelt werden, bevor und nachdem Sie ViewModel hinzugefügt haben:ViewModel
- Vor dem Hinzufügen von
ViewModel
:Wenn sich die Konfiguration ändert, z. B. wenn der Bildschirm gedreht wird, wird das Game-Fragment zerstört und neu erstellt. Die Daten sind verloren. - Nachdem Sie
ViewModelhinzugefügt und die UI-Daten des Spielfragments inViewModelverschoben haben, sind alle Daten, die das Fragment zum Anzeigen benötigt, jetztViewModel.
Wenn die App eine Konfigurationsänderung durchläuft, bleibtViewModelerhalten und die Daten werden beibehalten.

In dieser Aufgabe verschieben Sie die UI-Daten der App in die Klasse GameViewModel sowie die Methoden zur Verarbeitung der Daten. So bleiben die Daten bei Konfigurationsänderungen erhalten.
Schritt 1: Datenfelder und Datenverarbeitung in das ViewModel verschieben
Verschieben Sie die folgenden Datenfelder und Methoden von GameFragment nach GameViewModel:
- Verschieben Sie die Datenfelder
word,scoreundwordList. Achten Sie darauf, dasswordundscorenichtprivatesind.
Verschieben Sie die BindungsvariableGameFragmentBindingnicht, da sie Verweise auf die Ansichten enthält. Mit dieser Variablen wird das Layout aufgebläht, die Klick-Listener werden eingerichtet und die Daten werden auf dem Bildschirm angezeigt – alles Aufgaben des Fragments. - Verschieben Sie die Methoden
resetList()undnextWord(). Mit diesen Methoden wird entschieden, welches Wort auf dem Bildschirm angezeigt wird. - Verschieben Sie die Methodenaufrufe für
resetList()undnextWord()in der MethodeonCreateView()in deninit-Block vonGameViewModel.
Diese Methoden müssen sich iminit-Block befinden, da die Wortliste zurückgesetzt werden sollte, wennViewModelerstellt wird, nicht jedes Mal, wenn das Fragment erstellt wird. Sie können die Log-Anweisung iminit-Block vonGameFragmentlöschen.
Die Klick-Handler onSkip() und onCorrect() in GameFragment enthalten Code zum Verarbeiten der Daten und Aktualisieren der Benutzeroberfläche. Der Code zum Aktualisieren der Benutzeroberfläche sollte im Fragment verbleiben, der Code zum Verarbeiten der Daten muss jedoch in die ViewModel verschoben werden.
Fügen Sie vorerst die identischen Methoden an beiden Stellen ein:
- Kopieren Sie die Methoden
onSkip()undonCorrect()ausGameFragmentinGameViewModel. - Achten Sie im
GameViewModeldarauf, dass die MethodenonSkip()undonCorrect()nichtprivatesind, da Sie in Ihrem Fragment auf diese Methoden verweisen.
Hier ist der Code für die Klasse GameViewModel nach dem Refactoring:
class GameViewModel : ViewModel() {
// The current word
var word = ""
// The current score
var score = 0
// The list of words - the front of the list is the next word to guess
private lateinit var wordList: MutableList<String>
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
init {
resetList()
nextWord()
Log.i("GameViewModel", "GameViewModel created!")
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word = wordList.removeAt(0)
}
updateWordText()
updateScoreText()
}
/** Methods for buttons presses **/
fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
}Hier ist der Code für die Klasse GameFragment nach dem Refactoring:
/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var viewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
updateScoreText()
updateWordText()
return binding.root
}
/** Methods for button click handlers **/
private fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}Schritt 2: Verweise auf Klick-Handler und Datenfelder in „GameFragment“ aktualisieren
- Aktualisieren Sie in
GameFragmentdie MethodenonSkip()undonCorrect(). Entfernen Sie den Code zum Aktualisieren der Punktzahl und rufen Sie stattdessen die entsprechendenonSkip()- undonCorrect()-Methoden fürviewModelauf. - Da Sie die Methode
nextWord()in die KlasseViewModelverschoben haben, kann das Spiel-Fragment nicht mehr darauf zugreifen.
Ersetzen Sie inGameFragmentin den MethodenonSkip()undonCorrect()den Aufruf vonnextWord()durchupdateScoreText()undupdateWordText(). Mit diesen Methoden werden die Daten auf dem Bildschirm angezeigt.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}- Aktualisieren Sie im
GameFragmentdie Variablenscoreundword, damit dieGameViewModel-Variablen verwendet werden, da sich diese Variablen jetzt imGameViewModelbefinden.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}- Entfernen Sie in
GameViewModelin der MethodenextWord()die Aufrufe der MethodenupdateWordText()undupdateScoreText(). Diese Methoden werden jetzt überGameFragmentaufgerufen. - Erstellen Sie die App und achten Sie darauf, dass keine Fehler auftreten. Wenn Fehler auftreten, bereinigen Sie das Projekt und erstellen Sie es neu.
- Führen Sie die App aus und spielen Sie das Spiel durch. Drehen Sie das Gerät, während Sie sich auf dem Spielbildschirm befinden. Beachten Sie, dass die aktuelle Punktzahl und das aktuelle Wort nach der Änderung der Ausrichtung beibehalten werden.
Gut gemacht! Alle Daten Ihrer App werden jetzt in einem ViewModel gespeichert und bleiben daher bei Konfigurationsänderungen erhalten.
In dieser Aufgabe implementieren Sie den Click-Listener für die Schaltfläche End Game (Spiel beenden).
- Fügen Sie in
GameFragmenteine Methode namensonEndGame()hinzu. DieonEndGame()-Methode wird aufgerufen, wenn der Nutzer auf die Schaltfläche Spiel beenden tippt.
private fun onEndGame() {
}- Suchen Sie in
GameFragmentin der MethodeonCreateView()nach dem Code, mit dem Klick-Listener für die Schaltflächen Got It (Verstanden) und Skip (Überspringen) festgelegt werden. Legen Sie direkt unter diesen beiden Zeilen einen Klick-Listener für die Schaltfläche End Game (Spiel beenden) fest. Verwenden Sie die Bindungsvariablebinding. Rufen Sie im Click-Listener die MethodeonEndGame()auf.
binding.endGameButton.setOnClickListener { onEndGame() }- Fügen Sie in
GameFragmenteine Methode namensgameFinished()hinzu, um die App zum Ergebnisbildschirm zu navigieren. Übergeben Sie die Punktzahl als Argument mit Safe Args.
/**
* Called when the game is finished
*/
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score
NavHostFragment.findNavController(this).navigate(action)
}- Rufen Sie in der Methode
onEndGame()die MethodegameFinished()auf.
private fun onEndGame() {
gameFinished()
}- Führen Sie die App aus, spielen Sie das Spiel und wechseln Sie zwischen einigen Wörtern. Tippen Sie auf die Schaltfläche Spiel beenden . Die App wechselt zum Bildschirm mit dem Ergebnis, aber das Endergebnis wird nicht angezeigt. Das beheben Sie in der nächsten Aufgabe.
|
|
Wenn der Nutzer das Spiel beendet, wird in ScoreFragment keine Punktzahl angezeigt. Sie möchten, dass ein ViewModel den von ScoreFragment angezeigten Wert enthält. Sie übergeben den Punktwert während der ViewModel-Initialisierung mit dem Factory-Method-Muster.
Das Factory-Method-Muster ist ein Muster für die Erstellung, bei dem Factory-Methoden zum Erstellen von Objekten verwendet werden. Eine Factory-Methode ist eine Methode, die eine Instanz derselben Klasse zurückgibt.
In dieser Aufgabe erstellen Sie ein ViewModel mit einem parametrisierten Konstruktor für das Score-Fragment und einer Factory-Methode zum Instanziieren des ViewModel.
- Erstellen Sie unter dem Paket
scoreeine neue Kotlin-Klasse mit dem NamenScoreViewModel. Diese Klasse ist dieViewModelfür das Score-Fragment. - Erweitern Sie die Klasse
ScoreViewModelvonViewModel.. Fügen Sie einen Konstruktorparameter für die endgültige Punktzahl hinzu. Fügen Sie eineninit-Block mit einer Log-Anweisung hinzu. - Fügen Sie in der Klasse
ScoreViewModeleine Variable namensscorehinzu, um die endgültige Punktzahl zu speichern.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}- Erstellen Sie unter dem Paket
scoreeine weitere Kotlin-Klasse namensScoreViewModelFactory. Diese Klasse ist für die Instanziierung desScoreViewModel-Objekts verantwortlich. - Erweitern Sie die Klasse
ScoreViewModelFactoryausViewModelProvider.Factory. Fügen Sie einen Konstruktorparameter für die endgültige Punktzahl hinzu.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}- In
ScoreViewModelFactorywird in Android Studio ein Fehler zu einem nicht implementierten abstrakten Member angezeigt. Überschreiben Sie die Methodecreate(), um den Fehler zu beheben. Geben Sie in der Methodecreate()das neu erstellteScoreViewModel-Objekt zurück.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}- Erstellen Sie in
ScoreFragmentKlassenvariablen fürScoreViewModelundScoreViewModelFactory.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory- Initialisieren Sie in
ScoreFragmentinnerhalb vononCreateView()nach der Initialisierung der VariablenbindingdieviewModelFactory. Verwenden Sie dasScoreViewModelFactory. Übergeben Sie den endgültigen Wert aus dem Argument-Bundle als Konstruktorparameter anScoreViewModelFactory().
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)- Initialisieren Sie in
onCreateView(nach der Initialisierung vonviewModelFactorydas ObjektviewModel. Rufen Sie die MethodeViewModelProviders.of()auf und übergeben Sie den zugehörigen Kontext des Score-Fragments undviewModelFactory. Dadurch wird dasScoreViewModel-Objekt mit der in der KlasseviewModelFactorydefinierten Factory-Methode erstellt..
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)- Legen Sie in der Methode
onCreateView()nach der Initialisierung vonviewModelden Text der AnsichtscoreTextauf die inScoreViewModeldefinierte Endpunktzahl fest.
binding.scoreText.text = viewModel.score.toString()- Führen Sie die App aus und spielen Sie das Spiel. Gehen Sie einige oder alle Wörter durch und tippen Sie auf Spiel beenden. Im Score-Fragment wird jetzt das Endergebnis angezeigt.

- Optional: Prüfen Sie die
ScoreViewModel-Logs im Logcat, indem Sie nachScoreViewModelfiltern. Der Punktwert sollte angezeigt werden.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
In dieser Aufgabe haben Sie ScoreFragment implementiert, um ViewModel zu verwenden. Außerdem haben Sie gelernt, wie Sie mit der ViewModelFactory-Schnittstelle einen parametrisierten Konstruktor für ein ViewModel erstellen.
Glückwunsch! Sie haben die Architektur Ihrer App so geändert, dass eine der Android-Architekturkomponenten verwendet wird: ViewModel. Sie haben das Problem mit dem Lebenszyklus der App behoben und die Daten des Spiels bleiben jetzt bei Konfigurationsänderungen erhalten. Außerdem haben Sie gelernt, wie Sie einen parametrisierten Konstruktor zum Erstellen eines ViewModel mithilfe der ViewModelFactory-Schnittstelle erstellen.
Android Studio-Projekt: GuessTheWord
- In den Richtlinien für die Android-App-Architektur wird empfohlen, Klassen mit unterschiedlichen Verantwortlichkeiten zu trennen.
- Ein UI-Controller ist eine UI-basierte Klasse wie
ActivityoderFragment. UI-Controller sollten nur Logik enthalten, die UI- und Betriebssysteminteraktionen verarbeitet. Sie sollten keine Daten enthalten, die in der Benutzeroberfläche angezeigt werden sollen. Fügen Sie diese Daten in eineViewModelein. - In der Klasse
ViewModelwerden UI-bezogene Daten gespeichert und verwaltet. Mit der KlasseViewModelkönnen Daten Konfigurationsänderungen wie Bildschirmrotationen überstehen. ViewModelist eine der empfohlenen Android-Architekturkomponenten.ViewModelProvider.Factoryist eine Schnittstelle, mit der Sie einViewModel-Objekt erstellen können.
In der Tabelle unten werden UI-Controller mit den ViewModel-Instanzen verglichen, die Daten für sie enthalten:
UI-Controller | ViewModel |
Ein Beispiel für einen UI-Controller ist der | Ein Beispiel für ein |
Enthält keine Daten, die in der Benutzeroberfläche angezeigt werden können. | Enthält Daten, die vom UI-Controller in der Benutzeroberfläche angezeigt werden. |
Enthält Code zum Anzeigen von Daten und Nutzerereigniscode wie Klick-Listener. | Enthält Code für die Datenverarbeitung. |
Bei jeder Konfigurationsänderung wird sie zerstört und neu erstellt. | Wird nur zerstört, wenn der zugehörige UI-Controller dauerhaft entfernt wird – bei einer Aktivität, wenn die Aktivität beendet wird, oder bei einem Fragment, wenn das Fragment getrennt wird. |
Enthält Ansichten. | Darf niemals Verweise auf Aktivitäten, Fragmente oder Ansichten enthalten, da diese Konfigurationsänderungen nicht überstehen, |
Enthält einen Verweis auf die zugehörige | Enthält keinen Verweis auf den zugehörigen UI-Controller. |
Udacity-Kurs:
Android-Entwicklerdokumentation:
- ViewModel – Übersicht
- Umgang mit Lebenszyklen mit lebenszyklusbewussten Komponenten
- Leitfaden zur App-Architektur
ViewModelProviderViewModelProvider.Factory
Sonstiges:
- MVVM (Model-View-ViewModel)
- Designprinzip „Separation of Concerns“ (SoC)
- Factory Method Pattern
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
In welcher Klasse sollten Sie App-Daten speichern, um Datenverlust bei einer Änderung der Gerätekonfiguration zu vermeiden?
ViewModelLiveDataFragmentActivity
Frage 2
Eine ViewModel darf niemals Verweise auf Fragmente, Aktivitäten oder Ansichten enthalten. Richtig oder falsch?
- Richtig
- Falsch
Frage 3
Wann wird ein ViewModel zerstört?
- Wenn der zugehörige UI-Controller bei einer Änderung der Geräteausrichtung zerstört und neu erstellt wird.
- Bei einer Änderung der Ausrichtung.
- Wenn der zugehörige UI-Controller beendet (bei einer Aktivität) oder getrennt (bei einem Fragment) wurde.
- Wenn der Nutzer die Zurück-Schaltfläche drückt.
Frage 4
Wozu dient die ViewModelFactory-Schnittstelle?
- Instanziieren eines
ViewModel-Objekts. - Daten bei Änderungen der Ausrichtung beibehalten
- Die auf dem Bildschirm angezeigten Daten werden aktualisiert.
- Benachrichtigungen erhalten, wenn die App-Daten geändert werden.
Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Android Kotlin Fundamentals-Codelabs.




