Dieses Codelab ist Teil des Android Kotlin Fundamentals-Kurss. Sie profitieren von diesem Kurs, wenn Sie nacheinander die Codelabs durcharbeiten. Alle Kurs-Codelabs finden Sie auf der Landingpage für Kotlin-Grundlagen für Android-Entwickler.
Titelbildschirm |
Spiele-Bildschirm |
Index erstellen |
Einführung
In diesem Codelab lernen Sie eine der Android-Architekturkomponenten ViewModel
kennen:
- Mit der Klasse
ViewModel
speichern und verwalten Sie UI-bezogene Daten für den Lebenszyklus. Mit derViewModel
-Klasse werden Daten auch bei Änderungen der Gerätekonfiguration übernommen, z. B. Bildschirmdrehungen und Änderungen der Tastaturverfügbarkeit. - Sie verwenden die Klasse
ViewModelFactory
, um das ObjektViewModel
zu instanziieren und zurückzugeben, das Konfigurationsänderungen übersteht.
Was Sie bereits wissen sollten
- Grundlegende Android-Apps in Kotlin erstellen.
- Mit dem Navigationsdiagramm die Navigation in Ihrer App implementieren
- Code hinzufügen, um zwischen den Zielen Ihrer App zu wechseln und Daten zwischen Navigationszielen zu übergeben
- Funktionsweise der Aktivitäts- und Fragmentlebenszyklen
- Anleitung zum Hinzufügen von Logging-Informationen zu einer App und zum Lesen von Logs mit Logcat in Android Studio
Lerninhalte
- Wie Sie die empfohlene Android-App-Architektur verwenden
- Wie werden die Klassen
Lifecycle
,ViewModel
undViewModelFactory
in Ihrer App verwendet? - UI-Daten aufgrund von Änderungen an der Gerätekonfiguration beibehalten.
- Was das Factory-Design ist und wie es verwendet wird
- So lässt sich ein
ViewModel
-Objekt über die SchnittstelleViewModelProvider.Factory
erstellen.
Aufgaben
- Füge der App ein
ViewModel
hinzu, um App-Daten zu speichern, damit die Daten nach einer Konfigurationsänderung übernommen werden. - Sie können
ViewModelFactory
und das Designmuster für Factory-Methoden verwenden, um einViewModel
-Objekt mit Konstruktorparametern zu instanziieren.
In Modul 5 der Lektion 5 entwickeln Sie die GuessTheWord-App, die mit dem Startcode beginnt. GuessTheWord ist ein Spiel für zwei Spieler, das im Spiel Charades entwickelt und in dem Spieler die höchstmögliche Punktzahl erzielen.
Der erste Spieler betrachtet die Wörter in der App und agiert nacheinander. Dabei wird dem zweiten Spieler das Wort nicht angezeigt. Der zweite Spieler versucht, das Wort zu erraten.
Zum Spielen öffnet der erste Spieler die App auf dem Gerät und sieht ein Wort, z. B. „Gitarre“, unten.
Der erste Spieler führt das Wort aus und achte darauf, nicht das Wort selbst zu sagen.
- Wenn der zweite Spieler das Wort richtig errät, klickt der erste Spieler auf OK. 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 der Wörter um eins verringert und zum nächsten Wort springen.
- Klicken Sie auf die Schaltfläche Spiel beenden, um das Spiel zu beenden. Diese Funktion befindet sich im Starter-Code für das erste Codelab der Serie.
In dieser Aufgabe laden Sie die Starter-App herunter, führen sie aus und prüfen den Code.
Schritt 1: Erste Schritte
- Laden Sie den Startcode von GuessTheWord herunter und öffnen Sie das Projekt in Android Studio.
- Führen Sie die App auf einem Android-Gerät oder in einem Emulator aus.
- Tippen Sie auf die Tasten. Beachten Sie, dass mit der Schaltfläche Überspringen das nächste Wort angezeigt und die Punktzahl um eins verringert wird. Mit der Schaltfläche OK wird das nächste Wort angezeigt und die Punktzahl um eins erhöht. Die Schaltfläche Spiel beenden ist nicht implementiert, also passiert nichts, wenn du darauf tippst.
Schritt 2: Code Schritt-für-Schritt-Anleitung ausführen
- Sieh dir den Code in Android Studio an, um eine Vorstellung davon zu bekommen, wie die App funktioniert.
- Sehen Sie sich die unten beschriebenen Dateien an, die besonders wichtig sind.
Hauptaktivität.kt
Diese Datei enthält nur den standardmäßigen, von der Vorlage generierten Code.
res/layout/main_activity.xml
Diese Datei enthält das Hauptlayout der App. Die NavHostFragment
hostet die anderen Fragmente, während der Nutzer durch die App navigiert.
UI-Fragmente
Der Starter-Code hat drei Fragmente in drei verschiedenen Paketen unter dem com.example.android.guesstheword.screens
-Paket:
title/TitleFragment
für den Titelbildschirmgame/GameFragment
für Spielebildschirmscore/ScoreFragment
für die Punktezahl
Bildschirme/Titel/Titelfragment.kt
Das Titelfragment ist der erste Bildschirm, der angezeigt wird, wenn die App gestartet wird. Ein Klick-Handler ist auf die Schaltfläche Wiedergabe festgelegt, über die Sie zum Spielbildschirm gelangen können.
Bildschirme/Spiele/GameFragment.kt
Das ist das Fragment, in dem die meisten Aktionen des Spiels stattfinden:
- Variablen werden für das aktuelle Wort und die aktuelle Punktzahl definiert.
- Die
wordList
, die in derresetList()
-Methode definiert ist, ist eine Beispielliste der Wörter, die im Spiel verwendet werden sollen. - Die Methode
onSkip()
ist der Klick-Handler für die Schaltfläche Überspringen. Der Wert verringert sich um 1, anschließend wird mit dernextWord()
-Methode das nächste Wort angezeigt. - Die Methode
onCorrect()
ist der Klick-Handler für die Schaltfläche OK. Diese Methode wird ähnlich wie die MethodeonSkip()
implementiert. Der einzige Unterschied besteht darin, dass diese Methode mit 1 bewertet wird und nicht subtrahiert wird.
Screens/Score/ScoreFragment.kt
ScoreFragment
ist der letzte Bildschirm im Spiel und das Spielergebnis wird angezeigt. In diesem Codelab fügen Sie die Implementierung hinzu, damit dieser Bildschirm angezeigt wird und die endgültige Punktzahl angezeigt wird.
res/navigation/main_navigation.xml
Das Navigationsdiagramm zeigt, wie die Fragmente über die Navigation miteinander verbunden sind:
- Über das Titelfragment kann der Nutzer das Spielfragment aufrufen.
- Der Nutzer kann über das Spielfragment zum Wertfragment gelangen.
- Über das Punktzahlfragment kann der Nutzer zurück zum Spielfragment wechseln.
Bei dieser Aufgabe finden Sie Probleme mit der Start-App von GuessTheWord.
- Führen Sie den Startcode aus und spielen Sie das Spiel mit einigen Wörtern. Tippen Sie nach jedem Wort auf Überspringen oder Ok.
- Auf dem Spielbildschirm sind jetzt ein Wort und der aktuelle Spielstand zu sehen. Bildschirmausrichtung ändern, indem das Gerät oder der Emulator gedreht wird Beachten Sie, dass die aktuelle Punktzahl verloren geht.
- Führen Sie das Spiel noch ein paar Wörter aus. Wenn auf dem Bildschirm ein Spielergebnis angezeigt wird, schließen Sie die App und öffnen Sie sie wieder. Das Spiel beginnt von vorn, da der App-Status nicht gespeichert wird.
- Spielen Sie das Spiel mit einigen Wörtern und tippen Sie dann auf die Schaltfläche Spiel beenden. Beachte, dass nichts passiert.
Probleme mit der App:
- Der Startstatus der App wird bei Konfigurationsänderungen nicht gespeichert und wiederhergestellt, z. B. wenn sich die Geräteausrichtung ändert oder die App heruntergefahren und neu gestartet wird.
Sie können das Problem mit demonSaveInstanceState()
-Callback beheben. Wenn Sie die MethodeonSaveInstanceState()
verwenden, müssen Sie jedoch zusätzlichen Code schreiben, um den Zustand in einem Paket zu speichern, und dann die Logik zum Abrufen dieses Status implementieren. Außerdem ist die Menge der gespeicherten Daten minimal. - Der Spielbildschirm öffnet den Spielstand nicht, wenn der Nutzer auf die Schaltfläche Spiel beenden tippt.
Sie können diese Probleme mithilfe der Komponenten der App-Architektur in diesem Codelab beheben.
Anwendungsarchitektur
Mithilfe der App-Architektur können Sie Ihre Apps und die Beziehungen zu ihnen so gestalten, dass der Code organisiert ist, in bestimmten Szenarien gut funktioniert und einfach funktioniert. In diesem Satz mit vier Codelabs entsprechen die Verbesserungen der GuessTheWord-App den Richtlinien für die Android-App-Architektur und Sie verwenden die Komponenten der Android-Architektur. Die Android-App-Architektur ähnelt dem MVVM-Architekturmuster (model-view-viewmodel).
Die GuessTheWord-App folgt dem Grundsatz zur Trennung von Bedenken und ist in Klassen unterteilt, wobei jede Klasse einem anderen Thema entspricht. In diesem ersten Codelab der Lektion bearbeiten Sie einen UI-Controller, einen ViewModel
und einen ViewModelFactory
.
UI-Controller
Ein UI-Controller ist eine UI-basierte Klasse wie Activity
oder Fragment
. Ein UI-Controller sollte nur eine Logik enthalten, die die UI und Betriebssysteminteraktionen verarbeitet, wie z. B. Aufrufe und Erfassung der Nutzereingaben. Setzen Sie keine Entscheidungslogik, wie z. B. eine Logik, durch die der anzuzeigende Text bestimmt wird, in den UI-Controller.
Im GuessTheWord-Startcode sind die UI-Controller die drei Fragmente: GameFragment
, ScoreFragment,
und TitleFragment
. Gemäß dem Konzept der Trennung von Bedenken ist das GameFragment
nur dafür verantwortlich, Spielelemente auf dem Bildschirm anzuzeigen und zu wissen, wann der Nutzer auf die Schaltflächen tippt, und nichts mehr. Wenn der Nutzer auf eine Schaltfläche tippt, werden diese Informationen an GameViewModel
gesendet.
ViewModel
Ein ViewModel
enthält Daten, die in einem Fragment oder einer Aktivität angezeigt werden sollen, die mit der ViewModel
verknüpft sind. Ein ViewModel
kann einfache Berechnungen und Transformationen für Daten vornehmen, um die Daten für die Anzeige durch den UI-Controller vorzubereiten. In dieser Architektur führt der ViewModel
die Entscheidungsfindung aus.
Der GameViewModel
enthält Daten wie den Wert (Punktzahl), die Wortliste und das aktuelle Wort, da diese Daten auf dem Bildschirm angezeigt werden. Der GameViewModel
enthält auch die Geschäftslogik für einfache Berechnungen, um den aktuellen Zustand der Daten zu ermitteln.
Modellmodell ansehen
Ein ViewModelFactory
instanziiert ViewModel
-Objekte mit oder ohne Konstruktorparameter.
In späteren Codelabs erhalten Sie weitere Informationen zu Komponenten der Android-Architektur, die mit UI-Controllern und ViewModel
zusammenhängen.
Die Klasse ViewModel
wurde entwickelt, um die UI-bezogenen Daten zu speichern und zu verwalten. In dieser App ist jede ViewModel
einem Fragment zugeordnet.
In dieser Aufgabe fügen Sie Ihre erste ViewModel
zu Ihrer App hinzu, die GameViewModel
für die GameFragment
. Außerdem erfahren Sie, was das ViewModel
bedeutet, dass der Lebenszyklus berücksichtigt wird.
Schritt 1: GameViewModel-Klasse hinzufügen
- Öffnen Sie die Datei
build.gradle(module:app)
. Füge die Gradle-Abhängigkeit für denViewModel
im Blockdependencies
hinzu.
Wenn du die aktuelle Version der Bibliothek verwendest, sollte die Lösungs-App wie erwartet kompiliert werden. Ist dies nicht der Fall, versuchen Sie, das Problem zu beheben, oder kehren Sie zur unten angezeigten Version zurück.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
- Erstellen Sie im Ordner
screens/game/
des Pakets eine neue Kotlin-Klasse mit dem NamenGameViewModel
. GameViewModel
muss die abstrakte KlasseViewModel
erweitern.- Damit Sie besser nachvollziehen können, wie
ViewModel
den 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
ViewModel
wird gelöscht, wenn das zugehörige Fragment getrennt oder die Aktivität beendet wurde. Direkt vor dem Löschen von ViewModel
wird der onCleared()
-Callback aufgerufen, um die Ressourcen zu bereinigen.
- Überschreibe in der
GameViewModel
-Klasse dieonCleared()
-Methode. - Fügen Sie eine Log-Anweisung in
onCleared()
ein, um denGameViewModel
-Lebenszyklus zu verfolgen.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
Schritt 3: GameViewModel dem Spielfragment zuordnen
Ein ViewModel
muss mit einem UI-Controller verknüpft werden. Zum Verknüpfen der beiden erstellen Sie einen Verweis auf die ViewModel
im UI-Controller.
In diesem Schritt erstellen Sie einen Verweis auf die GameViewModel
im zugehörigen UI-Controller: GameFragment
.
- Fügen Sie in der Klasse
GameFragment
ein Feld vom TypGameViewModel
auf oberster Ebene als Klassenvariable hinzu.
private lateinit var viewModel: GameViewModel
Schritt 4: ViewModel initialisieren
Während Konfigurationsänderungen wie Bildschirmdrehungen werden UI-Controller wie Fragmente neu erstellt. ViewModel
-Instanzen sind jedoch erhalten. Wenn du die ViewModel
-Instanz mit der Klasse ViewModel
erstellst, wird bei jeder Neuerstellung des Fragments ein neues Objekt erstellt. Erstellen Sie stattdessen die ViewModel
-Instanz mit einem ViewModelProvider
.
So funktioniert ViewModelProvider
:
ViewModelProvider
gibt einen vorhandenenViewModel
zurück, wenn einer vorhanden ist, oder erstellt einen neuen, wenn dieser nicht bereits vorhanden ist.ViewModelProvider
erstellt eineViewModel
-Instanz in Verbindung mit dem angegebenen Bereich (einer Aktivität oder einem Fragment).- Die erstellte
ViewModel
wird aufbewahrt, solange der Bereich aktiv ist. Wenn der Bereich beispielsweise ein Fragment ist, wird dieViewModel
beibehalten, bis das Fragment getrennt wird.
Initialisiere ViewModel
mit der ViewModelProviders.of()
-Methode, um eine ViewModelProvider
zu erstellen:
- Initialisiere in der Klasse
GameFragment
die VariableviewModel
. Fügen Sie diesen Code nach der Definition der Bindungsvariablen inonCreateView()
ein. Verwende die MethodeViewModelProviders.of()
und übergib den zugehörigenGameFragment
-Kontext und dieGameViewModel
-Klasse. - Fügen Sie über der Initialisierung des
ViewModel
-Objekts eine Log-Anweisung hinzu, um den MethodenaufrufViewModelProviders.of()
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
. Tippen Sie auf Ihrem Gerät oder Emulator auf die Schaltfläche Wiedergabe. Der Spielbildschirm wird geöffnet.
Wie im Logcat gezeigt, ruft dieonCreateView()
-Methode vonGameFragment
dieViewModelProviders.of()
-Methode auf, um dieGameViewModel
zu erstellen. Die Logging-Anweisungen, die duGameFragment
undGameViewModel
hinzugefügt hast, werden im Logcat angezeigt.
- Aktivieren Sie die Einstellung für das automatische Drehen auf Ihrem Gerät oder in Ihrem Emulator und ändern Sie die Bildschirmausrichtung einige Male. Die
GameFragment
wird jedes Mal gelöscht und neu erstellt, sodassViewModelProviders.of()
jedes Mal aufgerufen wird. DieGameViewModel
wird aber nur einmal erstellt und nicht für jeden Aufruf neu erstellt oder gelöscht.
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 Spielfragment. Das
GameFragment
wurde gelöscht. Das zugehörigeGameViewModel
wird ebenfalls gelöscht 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!
ViewModel
bleibt auch bei Konfigurationsänderungen bestehen. Sie eignet sich daher gut für Daten, bei denen Konfigurationsänderungen übernommen werden müssen:
- Füge Daten hinzu, die auf dem Bildschirm angezeigt werden sollen, und Code, um diese Daten in der
ViewModel
zu verarbeiten. ViewModel
sollte niemals Verweise auf Fragmente, Aktivitäten oder Datenansichten enthalten, da Aktivitäten, Fragmente und Datenansichten keine Änderungen an der Konfiguration überstehen.
So werden Daten der Benutzeroberfläche GameFragment
in der Starter-App vor dem Hinzufügen von ViewModel
und nach dem Hinzufügen von ViewModel
verarbeitet:
- Bevor du
ViewModel
hinzufügst:
Wenn die App eine Konfigurationsänderung durchläuft, z. B. eine Bildschirmdrehung, wird das Spielfragment gelöscht und neu erstellt. Die Daten gehen verloren. - Nachdem du
ViewModel
hinzugefügt und die UI-Daten des Spielfragments inViewModel
verschoben hast:
Alle Daten, die das Fragment anzeigen soll, sind jetzt dieViewModel
. Wenn die Anwendung eine Konfigurationsänderung durchläuft, bleibtViewModel
bestehen und die Daten bleiben erhalten.
Bei dieser Aufgabe verschieben Sie die UI-Daten der App in die GameViewModel
-Klasse sowie die Methoden zur Datenverarbeitung. So bleiben die Daten während der Konfigurationsänderungen erhalten.
Schritt 1: Datenfelder und Datenverarbeitung in „ViewModel“ verschieben
Verschieben Sie die folgenden Datenfelder und Methoden aus GameFragment
in GameViewModel
:
- Verschieben Sie die Datenfelder
word
,score
undwordList
.word
undscore
dürfen nichtprivate
sein.
Verschieben Sie die BindungsvariableGameFragmentBinding
nicht, da sie Verweise auf die Ansichten enthält. Mit dieser Variablen wird das Layout aufgebläht, die Klick-Listener eingerichtet und die Daten auf dem Bildschirm angezeigt – die Verantwortlichkeiten des Fragments. - Verschieben Sie die Methoden
resetList()
undnextWord()
. Mit diesen Methoden wird festgelegt, welches Wort auf dem Bildschirm angezeigt wird. - Verschieben Sie innerhalb der
onCreateView()
-Methode die Aufrufe der Methode nachresetList()
undnextWord()
in deninit
-Block derGameViewModel
.
Diese Methoden müssen sich iminit
-Block befinden, da du die Wortliste bei der Erstellung vonViewModel
zurücksetzen solltest, und nicht bei jeder Erstellung des Fragments. Sie können die Loganweisung iminit
-Block vonGameFragment
löschen.
Die Klick-Handler onSkip()
und onCorrect()
in GameFragment
enthalten Code zum Verarbeiten der Daten und zum Aktualisieren der Benutzeroberfläche. Der Code zum Aktualisieren der Benutzeroberfläche muss im Fragment bleiben, aber der Code zum Verarbeiten der Daten muss in ViewModel
verschoben werden.
Fügen Sie an beiden Stellen die gleichen Methoden ein:
- Kopieren Sie die Methoden
onSkip()
undonCorrect()
ausGameFragment
inGameViewModel
. - Achten Sie darauf, dass in
GameViewModel
die MethodenonSkip()
undonCorrect()
nichtprivate
sind, da Sie auf diese Methoden aus dem Fragment verweisen.
Hier ist der Code für die GameViewModel
-Klasse nach Refaktorierung:
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 Refaktorierung:
/**
* 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
- Aktualisiere in
GameFragment
die MethodenonSkip()
undonCorrect()
. Entfernen Sie den Code, um den Wert zu aktualisieren, und rufen Sie stattdessen die entsprechenden MethodenonSkip()
undonCorrect()
inviewModel
auf. - Weil du die
nextWord()
-Methode in dieViewModel
verschoben hast, kann das Spielfragment nicht mehr darauf zugreifen.
Ersetzen Sie in 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 in den
GameFragment
-Variablenscore
undword
, damit die VariablenGameViewModel
verwendet werden, weil diese Variablen nun in derGameViewModel
sind.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}
- Entfernen Sie in der Methode
GameViewModel
innerhalb der MethodenextWord()
die Aufrufe für die MethodenupdateWordText()
undupdateScoreText()
. Diese Methoden werden jetzt ausGameFragment
aufgerufen. - Erstellen Sie die App und stellen Sie sicher, dass sie keine Fehler enthält. Bei Fehlern können Sie das Projekt bereinigen und neu erstellen.
- Führen Sie die App aus und spielen Sie das Spiel mit einigen Wörtern. Drehen Sie das Gerät, während Sie sich auf dem Spielbildschirm befinden. Beachten Sie, dass der aktuelle Wert und das aktuelle Wort nach der Änderung der Ausrichtung beibehalten werden.
Gut gemacht! Alle Ihre App-Daten werden jetzt in einem ViewModel
gespeichert, sodass sie bei Konfigurationsänderungen erhalten bleiben.
In dieser Aufgabe implementieren Sie den Klick-Listener für die Schaltfläche Spiel beenden.
- Füge in
GameFragment
eine Methode namensonEndGame()
hinzu. Die MethodeonEndGame()
wird aufgerufen, wenn der Nutzer auf die Schaltfläche Spiel beenden tippt.
private fun onEndGame() {
}
- Suchen Sie in der
onCreateView()
-Methode zuGameFragment
den Code, der die Klick-Listener für die Schaltflächen Ok und Überspringen festlegt. Legen Sie direkt unter diesen beiden Zeilen einen Klick-Listener für die Schaltfläche Spiel beenden fest. Verwenden Sie die Bindungsvariablebinding
. Rufen Sie im Klick-Listener die MethodeonEndGame()
auf.
binding.endGameButton.setOnClickListener { onEndGame() }
- Fügen Sie in
GameFragment
eine Methode namensgameFinished()
hinzu, um zur App zu gelangen und zum Punktzahlbildschirm zu gelangen. Übergeben Sie den Wert mit Safe Args als Argument.
/**
* 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)
}
- Rufe in der Methode
onEndGame()
die MethodegameFinished()
auf.
private fun onEndGame() {
gameFinished()
}
- Führe die App aus, spiele das Spiel und durchlaufe einige Wörter. Tippen Sie auf die Schaltfläche Spiel beenden. Beachten Sie, dass die App den Punktestand anzeigt, allerdings wird der endgültige Wert nicht angezeigt. Dieses Problem beheben Sie in der nächsten Aufgabe.
Wenn der Nutzer das Spiel beendet, wird der Optimierungsfaktor durch ScoreFragment
nicht angezeigt. Sie möchten, dass ein ViewModel
die Punktzahl auf ScoreFragment
festhält. Sie übergeben den Punktzahl-Wert während der ViewModel
-Initialisierung mit dem Factory-Muster-Muster.
Das Factory-Muster ist ein Designdesign, bei dem Objekte mithilfe von Factory-Methoden erstellt werden. Eine Factory-Methode ist eine Methode, die eine Instanz derselben Klasse zurückgibt.
In dieser Aufgabe erstellen Sie eine ViewModel
mit einem parametrisierten Konstruktor für das Punktzahlfragment und einer Factory-Methode zur Instanziierung des ViewModel
.
- Erstellen Sie unter dem Paket
score
eine neue Kotlin-Klasse mit dem NamenScoreViewModel
. Diese Klasse ist dieViewModel
für das Punktzahlfragment. - Erweitern Sie die
ScoreViewModel
-Klasse vonViewModel.
. Fügen Sie einen Konstruktorparameter für die endgültige Punktzahl hinzu. Fügen Sie eineninit
-Block mit einer Loganweisung hinzu. - Fügen Sie in der Klasse
ScoreViewModel
eine Variable namensscore
hinzu, um die Gesamtpunktzahl 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
score
eine weitere Kotlin-Klasse mit dem NamenScoreViewModelFactory
. Diese Klasse ist für die Instanziierung desScoreViewModel
-Objekts verantwortlich. - Erweitern Sie die
ScoreViewModelFactory
-Klasse vonViewModelProvider.Factory
. Fügen Sie einen Konstruktorparameter für die Gesamtpunktzahl hinzu.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
- In
ScoreViewModelFactory
wird in Android Studio ein Fehler in Bezug auf ein nicht implementiertes abstraktes Mitglied angezeigt. Überschreiben Sie diecreate()
-Methode, um den Fehler zu beheben. Gib in dercreate()
-Methode 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
ScoreFragment
die Klassenvariablen fürScoreViewModel
undScoreViewModelFactory
.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
- Initialisiere in
ScoreFragment
innerhalb vononCreateView()
nach der Initialisierung der Variablebinding
denviewModelFactory
. VerwendeScoreViewModelFactory
. Übergeben Sie die endgültige Punktzahl aus dem Argumentpaket als Konstruktorparameter anScoreViewModelFactory()
.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
- Initialisiere nach
onCreateView(
viewModelFactory
das ObjektviewModel
. Rufen Sie die MethodeViewModelProviders.of()
auf, übergeben Sie den zugehörigen Bewertungsfragment-Kontext undviewModelFactory
. Dadurch wird das ObjektScoreViewModel
mit der in derviewModelFactory
-Klasse definierten Factory-Methode erstellt..
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)
- Legen Sie nach der Initialisierung von
viewModel
inonCreateView()
den Text der AnsichtscoreText
auf den endgültigen Wert fürScoreViewModel
fest.
binding.scoreText.text = viewModel.score.toString()
- Führe deine App aus und spiel das Game. Gehen Sie einzelne Wörter oder alle Wörter durch und tippen Sie auf Spiel beenden. Das Bewertungsfragment zeigt jetzt die endgültige Punktzahl an.
- Optional: Prüfen Sie die
ScoreViewModel
-Protokolle im Logcat, indem Sie nachScoreViewModel
filtern. Der Wert 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
für die Verwendung von ViewModel
implementiert. Außerdem haben Sie gelernt, wie Sie über die Schnittstelle ViewModelFactory
einen parametrisierten Konstruktor für einen ViewModel
erstellen.
Glückwunsch! Sie haben die Architektur Ihrer App so geändert, dass eine der Android-Architekturkomponenten (ViewModel
) verwendet wird. Du hast das Lebenszyklusproblem in der App behoben. Jetzt sind die Konfigurationsdaten des Spiels erhalten. Außerdem haben Sie gelernt, wie Sie mit der Schnittstelle ViewModelFactory
einen parametrisierten Konstruktor zum Erstellen eines ViewModel
erstellen.
Android Studio-Projekt: GuessTheWord
- Die Android-Richtlinien für die App-Architektur empfehlen, Klassen mit unterschiedlichen Verantwortlichkeiten zu trennen.
- Ein UI-Controller ist eine UI-basierte Klasse wie
Activity
oderFragment
. UI-Controller sollten nur Logik enthalten, die UI- und Betriebssysteminteraktionen verarbeitet. Sie sollten keine Daten enthalten, die in der UI angezeigt werden sollen. Füge diese Daten in einViewModel
ein. - In der Klasse
ViewModel
werden Daten im Zusammenhang mit der Benutzeroberfläche gespeichert und verwaltet. Mit derViewModel
-Klasse können Daten auch Konfigurationsänderungen wie Bildschirmdrehen überleben. ViewModel
ist eine der empfohlenen Android-Architekturkomponenten.ViewModelProvider.Factory
ist eine Schnittstelle, mit der Sie einViewModel
-Objekt erstellen können.
In der folgenden Tabelle finden Sie einen Vergleich der UI-Controller mit den ViewModel
-Instanzen, die Daten für sie enthalten:
UI-Controller | Modell aufrufen |
Ein Beispiel für einen UI-Controller ist der | Ein Beispiel für ein |
Enthält keine Daten, die in der UI angezeigt werden können. | Enthält Daten, die vom UI-Controller in der UI 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 wurde sie gelöscht und neu erstellt. | Wird nur gelöscht, wenn der zugehörige UI-Controller dauerhaft verschwindet – für eine Aktivität, wenn die Aktivität abgeschlossen ist, oder für ein Fragment, wenn das Fragment getrennt ist. |
Enthält Aufrufe | Darf keine Verweise auf Aktivitäten, Fragmente oder Ansichten enthalten, da sie Konfigurationsänderungen nicht überleben, aber |
Enthält einen Verweis auf die verknüpfte | Enthält keine Referenz auf den verknüpften UI-Controller. |
Udacity-Kurs:
Android-Entwicklerdokumentation:
- ViewModel-Übersicht
- Lebenszyklus mit Lebenszyklus-sichtbaren Komponenten verarbeiten
- Leitfaden zur App-Architektur
ViewModelProvider
ViewModelProvider.Factory
Sonstiges:
- MVVM-Architektur (model-view-viewmodel)
- Trennung von Bedenken (SoC)
- Factory-Muster
In diesem Abschnitt werden mögliche Hausaufgaben für Schüler oder Studenten aufgeführt, die an diesem von einem Kursleiter geleiteten Codelab arbeiten. Die Lehrkraft kann Folgendes tun:
- Bei Bedarf können Sie die entsprechenden Aufgaben zuweisen.
- Schülern mitteilen, wie sie Aufgaben für die Aufgabe abgeben
- Benoten Sie die Hausaufgaben.
Lehrkräfte können diese Vorschläge so oft oder so oft verwenden, wie sie möchten. anderen Aufgaben können sie nach Belieben zugewiesen werden.
Wenn Sie alleine an diesem Codelab arbeiten, können Sie Ihr Wissen mit diesen Hausaufgaben testen.
Diese Fragen beantworten
Frage 1
In welcher Klasse sollten Sie App-Daten speichern, um Datenverluste während der Änderung der Gerätekonfiguration zu vermeiden?
ViewModel
LiveData
Fragment
Activity
Frage 2
Ein ViewModel
sollte niemals Verweise auf Fragmente, Aktivitäten oder Ansichten enthalten. Richtig oder falsch?
- Richtig
- Falsch
Frage 3
Wann wird ViewModel
gelöscht?
- Wenn der zugehörige UI-Controller während einer Änderung der Geräteausrichtung gelöscht und neu erstellt wird.
- Änderungen der Ausrichtung
- Wenn der zugehörige UI-Controller fertig ist (wenn es eine Aktivität ist) oder getrennt (wenn er ein Fragment ist).
- Wenn der Nutzer die Schaltfläche „Zurück“ drückt
Frage 4
Welchen Zweck hat die ViewModelFactory
-Schnittstelle?
- Instanziiert ein
ViewModel
-Objekt. - Daten bei Änderungen der Ausrichtung beibehalten.
- Die auf dem Bildschirm angezeigten Daten werden aktualisiert.
- Ich erhalte Benachrichtigungen, wenn die App-Daten geändert werden.
Beginnen Sie mit der nächsten Lektion:
Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage zu Kotlin-Grundlagen für Android.