Ten moduł Codelab jest częścią kursu Android Kotlin Fundamentals. Najwięcej korzyści przyniesie Ci ukończenie wszystkich ćwiczeń w kolejności. Wszystkie ćwiczenia z tego kursu znajdziesz na stronie docelowej kursu Android Kotlin Fundamentals.
Ekran tytułowy |
Ekran gry |
Ekran wyników |
Wprowadzenie
W tych ćwiczeniach z programowania dowiesz się więcej o jednym z komponentów architektury Androida: ViewModel.
- Klasy
ViewModelużywasz do przechowywania danych związanych z interfejsem i zarządzania nimi w sposób uwzględniający cykl życia. KlasaViewModelumożliwia zachowanie danych po zmianach konfiguracji urządzenia, takich jak obracanie ekranu czy zmiany dostępności klawiatury. - Klasy
ViewModelFactoryużywasz do tworzenia instancji i zwracania obiektuViewModel, który przetrwa zmiany konfiguracji.
Co warto wiedzieć
- Jak tworzyć podstawowe aplikacje na Androida w języku Kotlin.
- Jak używać wykresu nawigacji do implementowania nawigacji w aplikacji.
- Jak dodać kod, który umożliwia przechodzenie między miejscami docelowymi aplikacji i przekazywanie danych między nimi.
- Jak działają cykle życia aktywności i fragmentów.
- Jak dodać informacje o logowaniu do aplikacji i odczytywać logi za pomocą Logcat w Android Studio.
Czego się nauczysz
- Jak korzystać z zalecanej architektury aplikacji na Androida.
- Jak używać w aplikacji klas
Lifecycle,ViewModeliViewModelFactory. - Jak zachować dane interfejsu podczas zmian konfiguracji urządzenia.
- Czym jest wzorzec projektowy metody fabrycznej i jak go używać.
- Jak utworzyć obiekt
ViewModelza pomocą interfejsuViewModelProvider.Factory.
Jakie zadania wykonasz
- Dodaj do aplikacji
ViewModel, aby zapisać dane aplikacji i zachować je po zmianach konfiguracji. - Użyj
ViewModelFactoryi wzorca projektowego metody fabrykującej, aby utworzyć instancję obiektuViewModelz parametrami konstruktora.
W ćwiczeniach z programowania w lekcji 5 tworzysz aplikację GuessTheWord, zaczynając od kodu początkowego. GuessTheWord to gra w kalambury dla 2 osób, w której gracze współpracują, aby uzyskać jak najwyższy wynik.
Pierwszy gracz patrzy na słowa w aplikacji i kolejno odgrywa każde z nich, uważając, aby nie pokazać słowa drugiemu graczowi. Drugi gracz próbuje odgadnąć słowo.
Aby rozpocząć grę, pierwszy gracz otwiera aplikację na urządzeniu i widzi słowo, np. „gitara”, jak pokazano na zrzucie ekranu poniżej.
Pierwszy gracz odgrywa słowo, uważając, aby go nie wypowiedzieć.
- Gdy drugi gracz odgadnie słowo, pierwszy gracz naciśnie przycisk Got It (Mam to), co zwiększy liczbę o 1 i wyświetli kolejne słowo.
- Jeśli drugi gracz nie odgadnie słowa, pierwszy gracz naciśnie przycisk Pomiń, co zmniejszy liczbę o 1 i spowoduje przejście do następnego słowa.
- Aby zakończyć grę, naciśnij przycisk Zakończ grę. (Ta funkcja nie jest dostępna w kodzie początkowym pierwszego laboratorium w tej serii).
W tym zadaniu pobierzesz i uruchomisz aplikację początkową oraz sprawdzisz kod.
Krok 1. Pierwsze kroki
- Pobierz kod początkowy aplikacji GuessTheWord i otwórz projekt w Android Studio.
- Uruchom aplikację na urządzeniu z Androidem lub w emulatorze.
- Naciśnij przyciski. Zauważ, że przycisk Pomiń wyświetla następne słowo i zmniejsza wynik o 1, a przycisk Rozumiem wyświetla następne słowo i zwiększa wynik o 1. Przycisk Zakończ grę nie jest zaimplementowany, więc po jego kliknięciu nic się nie dzieje.
Krok 2. Przejrzyj kod
- W Android Studio zapoznaj się z kodem, aby dowiedzieć się, jak działa aplikacja.
- Zapoznaj się z opisanymi poniżej plikami, które są szczególnie ważne.
MainActivity.kt
Ten plik zawiera tylko domyślny kod wygenerowany przez szablon.
res/layout/main_activity.xml
Ten plik zawiera główny układ aplikacji. NavHostFragment hostuje pozostałe fragmenty, gdy użytkownik porusza się po aplikacji.
Fragmenty interfejsu
Kod początkowy zawiera 3 fragmenty w 3 różnych pakietach w pakiecie com.example.android.guesstheword.screens:
title/TitleFragmentna ekranie tytułowymgame/GameFragment– ekran gry.score/ScoreFragment– ekran wyników
screens/title/TitleFragment.kt
Fragment tytułu to pierwszy ekran, który jest wyświetlany po uruchomieniu aplikacji. Do przycisku Graj przypisany jest moduł obsługi kliknięć, który umożliwia przejście do ekranu gry.
screens/game/GameFragment.kt
To główny fragment, w którym rozgrywa się większość akcji w grze:
- Zmienne są zdefiniowane dla bieżącego słowa i bieżącego wyniku.
wordListzdefiniowana w metodzieresetList()to przykładowa lista słów, które mają być używane w grze.- Metoda
onSkip()to moduł obsługi kliknięć przycisku Pomiń. Zmniejsza wynik o 1, a następnie wyświetla kolejne słowo za pomocą metodynextWord(). - Metoda
onCorrect()jest modułem obsługi kliknięć przycisku OK. Ta metoda jest implementowana podobnie do metodyonSkip(). Jedyna różnica polega na tym, że ta metoda dodaje 1 punkt do wyniku zamiast go odejmować.
screens/score/ScoreFragment.kt
ScoreFragment to ostatni ekran w grze, na którym wyświetla się ostateczny wynik gracza. W tym laboratorium kodowania dodasz implementację, która będzie wyświetlać ten ekran i pokazywać wynik końcowy.
res/navigation/main_navigation.xml
Graf nawigacji pokazuje, jak fragmenty są połączone za pomocą nawigacji:
- Z fragmentu tytułu użytkownik może przejść do fragmentu gry.
- Z fragmentu gry użytkownik może przejść do fragmentu wyniku.
- Z fragmentu z wynikami użytkownik może wrócić do fragmentu z grą.
W tym zadaniu znajdziesz problemy z aplikacją startową GuessTheWord.
- Uruchom kod początkowy i zagraj w grę, wpisując kilka słów. Po każdym słowie kliknij Pomiń lub OK.
- Na ekranie gry pojawi się słowo i aktualny wynik. Zmień orientację ekranu, obracając urządzenie lub emulator. Zwróć uwagę, że bieżący wynik został utracony.
- Zagraj jeszcze kilka razy. Gdy na ekranie gry pojawi się wynik, zamknij i ponownie otwórz aplikację. Zauważ, że gra rozpoczyna się od początku, ponieważ stan aplikacji nie jest zapisywany.
- Zagraj w grę, wpisując kilka słów, a potem kliknij przycisk Zakończ grę. Zauważ, że nic się nie dzieje.
Problemy w aplikacji:
- Aplikacja startowa nie zapisuje i nie przywraca stanu aplikacji podczas zmian konfiguracji, np. gdy zmienia się orientacja urządzenia lub gdy aplikacja jest zamykana i uruchamiana ponownie.
Ten problem możesz rozwiązać za pomocą wywołania zwrotnegoonSaveInstanceState(). Korzystanie z metodyonSaveInstanceState()wymaga jednak napisania dodatkowego kodu, który zapisuje stan w pakiecie i wdraża logikę pobierania tego stanu. Poza tym ilość danych, które można przechowywać, jest minimalna. - Gdy użytkownik kliknie przycisk Zakończ grę, ekran gry nie przechodzi do ekranu z wynikami.
Możesz rozwiązać te problemy, korzystając z komponentów architektury aplikacji, które poznasz w tym ćwiczeniu.
Architektura aplikacji
Architektura aplikacji to sposób projektowania klas aplikacji i relacji między nimi, dzięki któremu kod jest uporządkowany, dobrze działa w określonych scenariuszach i jest łatwy w użyciu. W tym zestawie 4 ćwiczeń z programowania ulepszenia, które wprowadzisz w aplikacji GuessTheWord, będą zgodne z wytycznymi dotyczącymi architektury aplikacji na Androida. Będziesz też używać komponentów architektury Androida. Architektura aplikacji na Androida jest podobna do wzorca architektonicznego MVVM (model-view-viewmodel).
Aplikacja GuessTheWord jest zgodna z zasadą projektowania rozdzielenia odpowiedzialności i jest podzielona na klasy, z których każda odpowiada za inną funkcję. W tym pierwszym module szkoleniowym lekcji klasy, z którymi będziesz pracować, to kontroler interfejsu, ViewModel i ViewModelFactory.
kontroler interfejsu,
Kontroler interfejsu to klasa oparta na interfejsie, np. Activity lub Fragment. Kontroler interfejsu powinien zawierać tylko logikę obsługującą interakcje z interfejsem i systemem operacyjnym, takie jak wyświetlanie widoków i przechwytywanie danych wprowadzanych przez użytkownika. Nie umieszczaj w kontrolerze interfejsu logiki podejmowania decyzji, np. logiki określającej tekst do wyświetlenia.
W kodzie początkowym aplikacji GuessTheWord kontrolerami interfejsu są 3 fragmenty: GameFragment, ScoreFragment, i TitleFragment. Zgodnie z zasadą projektowania „rozdzielenia odpowiedzialności” klasa GameFragment odpowiada tylko za rysowanie elementów gry na ekranie i wykrywanie, kiedy użytkownik dotyka przycisków. Gdy użytkownik kliknie przycisk, te informacje zostaną przekazane do GameViewModel.
ViewModel
ViewModel zawiera dane, które mają być wyświetlane we fragmencie lub aktywności powiązanej z ViewModel. ViewModel może wykonywać proste obliczenia i transformacje danych, aby przygotować je do wyświetlenia przez kontroler interfejsu. W tej architekturze ViewModel podejmuje decyzje.GameViewModel przechowuje dane, takie jak wartość wyniku, lista słów i bieżące słowo, ponieważ są to dane, które mają być wyświetlane na ekranie. GameViewModel zawiera też logikę biznesową do wykonywania prostych obliczeń, które pozwalają określić bieżący stan danych.
ViewModelFactory
Obiekt ViewModelFactory tworzy instancje obiektów ViewModel z parametrami konstruktora lub bez nich.

W dalszych ćwiczeniach dowiesz się więcej o innych komponentach architektury Androida, które są powiązane z kontrolerami interfejsu i ViewModel.
Klasa ViewModel służy do przechowywania danych związanych z interfejsem i zarządzania nimi. W tej aplikacji każdy ViewModel jest powiązany z 1 fragmentem.
W tym zadaniu dodasz do aplikacji pierwszy ViewModel, czyli GameViewModel dla GameFragment. Dowiesz się też, co oznacza, że ViewModel jest świadomy cyklu życia.
Krok 1. Dodaj klasę GameViewModel
- Otwórz plik
build.gradle(module:app). W blokudependenciesdodaj zależność Gradle dlaViewModel.
Jeśli używasz najnowszej wersji biblioteki, aplikacja z rozwiązaniem powinna się skompilować zgodnie z oczekiwaniami. Jeśli nie, spróbuj rozwiązać problem lub wróć do wersji podanej poniżej.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'- W folderze pakietu
screens/game/utwórz nową klasę Kotlin o nazwieGameViewModel. - Spraw, aby klasa
GameViewModelrozszerzała klasę abstrakcyjnąViewModel. - Aby pomóc Ci lepiej zrozumieć, jak
ViewModeljest uwzględniany w cyklu życia, dodaj blokinitz instrukcjąlog.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}Krok 2. Zastąp metodę onCleared() i dodaj rejestrowanie
Obiekt ViewModel jest niszczony, gdy powiązany z nim fragment zostanie odłączony lub gdy aktywność się zakończy. Tuż przed zniszczeniem obiektu ViewModel wywoływane jest wywołanie zwrotne onCleared(), aby wyczyścić zasoby.
- W klasie
GameViewModelzastąp metodęonCleared(). - Dodaj instrukcję logowania w
onCleared(), aby śledzić cykl życiaGameViewModel.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}Krok 3. Powiąż GameViewModel z fragmentem gry
ViewModel musi być powiązany z kontrolerem interfejsu. Aby je ze sobą powiązać, utwórz odwołanie do elementu ViewModel w kontrolerze interfejsu.
W tym kroku utworzysz odwołanie do elementu GameViewModel w odpowiednim kontrolerze interfejsu, czyli GameFragment.
- W klasie
GameFragmentdodaj pole typuGameViewModelna najwyższym poziomie jako zmienną klasy.
private lateinit var viewModel: GameViewModelKrok 4. Zainicjuj ViewModel
Podczas zmian konfiguracji, takich jak obracanie ekranu, kontrolery interfejsu, np. fragmenty, są ponownie tworzone. Jednak ViewModel instancji przetrwa. Jeśli utworzysz instancję ViewModel za pomocą klasy ViewModel, za każdym razem, gdy fragment zostanie ponownie utworzony, powstanie nowy obiekt. Zamiast tego utwórz instancję ViewModel za pomocą ViewModelProvider.

Jak działa ViewModelProvider:
- Funkcja
ViewModelProviderzwraca istniejący obiektViewModel, jeśli taki istnieje, lub tworzy nowy, jeśli jeszcze nie istnieje. ViewModelProvidertworzy instancjęViewModelpowiązaną z danym zakresem (aktywnością lub fragmentem).- Utworzony obiekt
ViewModeljest przechowywany tak długo, jak długo istnieje zakres. Jeśli na przykład zakres to fragment, elementViewModeljest zachowywany do momentu odłączenia fragmentu.
Zainicjuj ViewModel, używając metody ViewModelProviders.of() do utworzenia ViewModelProvider:
- W klasie
GameFragmentzainicjuj zmiennąviewModel. Umieść ten kod wonCreateView()po definicji zmiennej wiązania. Użyj metodyViewModelProviders.of()i przekaż powiązany kontekstGameFragmentoraz klasęGameViewModel. - Nad inicjalizacją obiektu
ViewModeldodaj instrukcję logowania, aby rejestrować wywołanie metodyViewModelProviders.of().
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)- Uruchom aplikację. W Android Studio otwórz panel Logcat i filtruj według
Game. Kliknij przycisk Odtwórz na urządzeniu lub emulatorze. Otworzy się ekran gry.
Jak widać w Logcat, metodaonCreateView()klasyGameFragmentwywołuje metodęViewModelProviders.of(), aby utworzyć obiektGameViewModel. Instrukcje logowania dodane do funkcjiGameFragmentiGameViewModelpojawią się w Logcat.

- Włącz ustawienie autoobracania na urządzeniu lub emulatorze i kilka razy zmień orientację ekranu.
GameFragmentjest za każdym razem niszczony i tworzony ponownie, więcViewModelProviders.of()jest wywoływany za każdym razem. ObiektGameViewModeljest jednak tworzony tylko raz i nie jest ponownie tworzony ani niszczony przy każdym wywołaniu.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- Zakończ grę lub wyjdź z fragmentu gry.
GameFragmentzostaje zniszczony. Powiązany obiektGameViewModelrównież zostanie zniszczony, a wywołanie zwrotneonCleared()zostanie wywołane.
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 przetrwa zmiany konfiguracji, więc jest dobrym miejscem na dane, które muszą przetrwać zmiany konfiguracji:
- Umieść dane, które mają być wyświetlane na ekranie, oraz kod do ich przetwarzania w
ViewModel. - Obiekt
ViewModelnigdy nie powinien zawierać odwołań do fragmentów, aktywności ani widoków, ponieważ aktywności, fragmenty i widoki nie przetrwają zmian konfiguracji.

Dla porównania zobacz, jak dane interfejsu GameFragment są obsługiwane w aplikacji startowej przed dodaniem ViewModel i po dodaniu ViewModel:
- Przed dodaniem
ViewModel:
gdy w aplikacji nastąpi zmiana konfiguracji, np. obrócenie ekranu, fragment gry zostanie zniszczony i utworzony ponownie. Dane zostaną utracone. - Po dodaniu
ViewModeli przeniesieniu danych interfejsu fragmentu gry doViewModel:
wszystkie dane potrzebne do wyświetlenia fragmentu znajdują się teraz wViewModel. Gdy aplikacja przechodzi zmianę konfiguracji,ViewModelpozostaje bez zmian, a dane są zachowywane.

W tym zadaniu przeniesiesz dane interfejsu aplikacji do klasy GameViewModel wraz z metodami przetwarzania danych. Dzięki temu dane są zachowywane podczas zmian konfiguracji.
Krok 1. Przenieś pola danych i przetwarzanie danych do ViewModel
Przenieś te pola danych i metody z GameFragment do GameViewModel:
- Przenieś pola danych
word,scoreiwordList. Upewnij się, żewordiscorenie są równeprivate.
Nie przenoś zmiennej wiążącejGameFragmentBinding, ponieważ zawiera ona odniesienia do widoków. Ta zmienna służy do rozszerzania układu, konfigurowania odbiorników kliknięć i wyświetlania danych na ekranie – są to zadania fragmentu. - Przenieś metody
resetList()inextWord(). Te metody decydują o tym, jakie słowo ma się wyświetlić na ekranie. - W metodzie
onCreateView()przenieś wywołania metodresetList()inextWord()do blokuinitmetodyGameViewModel.
Te metody muszą znajdować się w blokuinit, ponieważ listę słów należy zresetować, gdy tworzony jestViewModel, a nie za każdym razem, gdy tworzony jest fragment. Instrukcję logowania możesz usunąć w blokuinitw sekcjiGameFragment.
Obsługa kliknięć onSkip() i onCorrect() w GameFragment zawiera kod do przetwarzania danych i aktualizowania interfejsu. Kod aktualizujący interfejs użytkownika powinien pozostać we fragmencie, ale kod przetwarzający dane musi zostać przeniesiony do ViewModel.
Na razie umieść identyczne metody w obu miejscach:
- Skopiuj metody
onSkip()ionCorrect()zGameFragmentdoGameViewModel. - W
GameViewModelupewnij się, że metodyonSkip()ionCorrect()nie sąprivate, ponieważ będziesz się do nich odwoływać z fragmentu.
Oto kod klasy GameViewModel po refaktoryzacji:
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!")
}
}Oto kod klasy GameFragment po refaktoryzacji:
/**
* 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()
}
}Krok 2. Zaktualizuj odwołania do funkcji obsługi kliknięć i pól danych w klasie GameFragment
- Na stronie
GameFragmentzaktualizuj formy płatnościonSkip()ionCorrect(). Usuń kod, aby zaktualizować wynik, i zamiast tego wywołaj odpowiednie metodyonSkip()ionCorrect()na obiekcieviewModel. - Metoda
nextWord()została przeniesiona do klasyViewModel, więc fragment gry nie ma już do niej dostępu.
W klasieGameFragmentw metodachonSkip()ionCorrect()zastąp wywołanie metodynextWord()wywołaniami metodupdateScoreText()iupdateWordText(). Te metody wyświetlają dane na ekranie.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}- W
GameFragmentzaktualizuj zmiennescoreiword, aby używać zmiennychGameViewModel, ponieważ znajdują się one teraz wGameViewModel.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}- W
GameViewModelw metodzienextWord()usuń wywołania metodupdateWordText()iupdateScoreText(). Te metody są teraz wywoływane z poziomuGameFragment. - Skompiluj aplikację i upewnij się, że nie ma błędów. Jeśli wystąpią błędy, wyczyść i ponownie skompiluj projekt.
- Uruchom aplikację i zagraj w grę, używając kilku słów. Na ekranie gry obróć urządzenie. Zwróć uwagę, że po zmianie orientacji ekranu bieżący wynik i bieżące słowo pozostają bez zmian.
Brawo! Teraz wszystkie dane aplikacji są przechowywane w ViewModel, więc są zachowywane podczas zmian konfiguracji.
W tym zadaniu zaimplementujesz detektor kliknięć przycisku End Game (Zakończ grę).
- W
GameFragmentdodaj metodę o nazwieonEndGame(). MetodaonEndGame()zostanie wywołana, gdy użytkownik kliknie przycisk Zakończ grę.
private fun onEndGame() {
}- W
GameFragmentw metodzieonCreateView()znajdź kod, który ustawia odbiorniki kliknięć przycisków OK i Pomiń. Tuż pod tymi 2 wierszami ustaw odbiornik kliknięć przycisku End Game (Zakończ grę). Użyj zmiennej wiązaniabinding. W funkcji obsługi kliknięcia wywołaj metodęonEndGame().
binding.endGameButton.setOnClickListener { onEndGame() }- W
GameFragmentdodaj metodę o nazwiegameFinished(), która przeniesie użytkownika do ekranu z wynikiem. Przekaż wynik jako argument, używając 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)
}- W metodzie
onEndGame()wywołaj metodęgameFinished().
private fun onEndGame() {
gameFinished()
}- Uruchom aplikację, zagraj w grę i przejrzyj kilka słów. Kliknij przycisk Zakończ grę . Zauważ, że aplikacja przechodzi do ekranu z wynikami, ale nie wyświetla ostatecznego wyniku. Naprawisz to w następnym zadaniu.
|
|
Gdy użytkownik zakończy grę, ScoreFragment nie wyświetla wyniku. Chcesz, aby ViewModel przechowywał wynik, który ma być wyświetlany przez ScoreFragment. Wartość wyniku przekażesz podczas inicjowania ViewModel za pomocą wzorca metody fabrycznej.
Wzorzec metody fabrycznej to wzorzec projektowy tworzenia, który do tworzenia obiektów używa metod fabrycznych. Metoda fabrykująca to metoda, która zwraca instancję tej samej klasy.
W tym zadaniu utworzysz ViewModel ze sparametryzowanym konstruktorem fragmentu wyniku i metodą fabryczną do tworzenia instancji ViewModel.
- W pakiecie
scoreutwórz nową klasę Kotlin o nazwieScoreViewModel. Ta klasa będzieViewModeldla fragmentu wyniku. - Rozszerz klasę
ScoreViewModelz klasyViewModel.. Dodaj parametr konstruktora dla wyniku końcowego. Dodaj blokinitz instrukcją logowania. - W klasie
ScoreViewModeldodaj zmienną o nazwiescore, aby zapisać wynik końcowy.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}- W pakiecie
scoreutwórz kolejną klasę Kotlin o nazwieScoreViewModelFactory. Ta klasa będzie odpowiedzialna za tworzenie instancji obiektuScoreViewModel. - Przedłuż zajęcia
ScoreViewModelFactoryodViewModelProvider.Factory. Dodaj parametr konstruktora dla wyniku końcowego.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}- W
ScoreViewModelFactoryAndroid Studio wyświetla błąd dotyczący niezaimplementowanego abstrakcyjnego elementu. Aby naprawić ten błąd, zastąp metodęcreate(). W metodziecreate()zwróć nowo utworzony obiektScoreViewModel.
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")
}- W
ScoreFragmentutwórz zmienne klasy dlaScoreViewModeliScoreViewModelFactory.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory- W
ScoreFragmentwonCreateView()po zainicjowaniu zmiennejbindingzainicjuj zmiennąviewModelFactory. UżyjScoreViewModelFactory. Przekaż wynik końcowy z pakietu argumentów jako parametr konstruktora do funkcjiScoreViewModelFactory().
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)- W pliku
onCreateView(po zainicjowaniu obiektuviewModelFactoryzainicjuj obiektviewModel. Wywołaj metodęViewModelProviders.of(), przekaż powiązany kontekst fragmentu wyniku iviewModelFactory. Spowoduje to utworzenie obiektuScoreViewModelza pomocą metody fabrykującej zdefiniowanej w klasieviewModelFactory..
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)- W metodzie
onCreateView()po zainicjowaniuviewModelustaw tekst widokuscoreTextna wynik końcowy zdefiniowany wScoreViewModel.
binding.scoreText.text = viewModel.score.toString()- Uruchom aplikację i zagraj w grę. Przejrzyj niektóre lub wszystkie słowa i kliknij Zakończ grę. Zwróć uwagę, że fragment wyniku wyświetla teraz wynik końcowy.

- Opcjonalnie: sprawdź dzienniki
ScoreViewModelw narzędziu Logcat, filtrując je wedługScoreViewModel. Powinna być wyświetlana wartość oceny.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
W tym zadaniu zaimplementowano ScoreFragment, aby używać ViewModel. Dowiedzieliśmy się też, jak utworzyć konstruktor sparametryzowany dla ViewModel za pomocą interfejsu ViewModelFactory.
Gratulacje! Zmieniono architekturę aplikacji, aby korzystać z jednego ze składników architektury Androida, ViewModel. Problem z cyklem życia aplikacji został rozwiązany i dane gry są teraz zachowywane po zmianach konfiguracji. Dowiedzieliśmy się też, jak utworzyć konstruktor sparametryzowany do tworzenia obiektu ViewModel za pomocą interfejsu ViewModelFactory.
Projekt Android Studio: GuessTheWord
- Wskazówki dotyczące architektury aplikacji na Androida zalecają rozdzielanie klas o różnych zadaniach.
- Kontroler interfejsu to klasa oparta na interfejsie, np.
ActivitylubFragment. Kontrolery interfejsu powinny zawierać tylko logikę obsługującą interakcje z interfejsem i systemem operacyjnym. Nie powinny zawierać danych, które mają być wyświetlane w interfejsie. Umieść te dane wViewModel. - Klasa
ViewModelprzechowuje dane związane z interfejsem i nimi zarządza. KlasaViewModelumożliwia przetrwanie danych po zmianach konfiguracji, takich jak obracanie ekranu. ViewModeljest jednym z zalecanych składników architektury Androida.ViewModelProvider.Factoryto interfejs, za pomocą którego możesz utworzyć obiektViewModel.
W tabeli poniżej znajdziesz porównanie kontrolerów interfejsu z instancjami ViewModel, które przechowują dane na ich potrzeby:
Kontroler interfejsu | ViewModel |
Przykładem kontrolera interfejsu jest element | Przykładem |
Nie zawiera żadnych danych do wyświetlenia w interfejsie. | Zawiera dane, które kontroler interfejsu wyświetla w interfejsie. |
Zawiera kod do wyświetlania danych i kod zdarzeń użytkownika, np. odbiorniki kliknięć. | Zawiera kod do przetwarzania danych. |
Niszczone i tworzone ponownie przy każdej zmianie konfiguracji. | Niszczony tylko wtedy, gdy powiązany kontroler interfejsu znika na stałe – w przypadku aktywności, gdy się kończy, a w przypadku fragmentu, gdy jest odłączany. |
Zawiera widoki. | Nigdy nie powinny zawierać odwołań do aktywności, fragmentów ani widoków, ponieważ nie przetrwają one zmian konfiguracji, ale |
Zawiera odwołanie do powiązanego | Nie zawiera odniesienia do powiązanego kontrolera interfejsu. |
Kurs Udacity:
Dokumentacja dla deweloperów aplikacji na Androida:
- Omówienie ViewModel
- Obsługa cykli życia za pomocą komponentów uwzględniających cykl życia
- Przewodnik po architekturze aplikacji
ViewModelProviderViewModelProvider.Factory
Inne:
- Wzorzec architektury MVVM (model-view-viewmodel).
- Zasada projektowania rozdzielenia odpowiedzialności (SoC)
- Wzorzec metody fabrycznej
W tej sekcji znajdziesz listę możliwych zadań domowych dla uczniów, którzy wykonują ten moduł w ramach kursu prowadzonego przez instruktora. Nauczyciel musi:
- W razie potrzeby przypisz pracę domową.
- Poinformuj uczniów, jak przesyłać projekty.
- Oceń zadania domowe.
Instruktorzy mogą korzystać z tych sugestii w dowolnym zakresie i mogą zadawać inne zadania domowe, które uznają za odpowiednie.
Jeśli wykonujesz ten kurs samodzielnie, możesz użyć tych zadań domowych, aby sprawdzić swoją wiedzę.
Odpowiedz na te pytania
Pytanie 1
Aby uniknąć utraty danych podczas zmiany konfiguracji urządzenia, w której klasie należy zapisać dane aplikacji?
ViewModelLiveDataFragmentActivity
Pytanie 2
ViewModel nie powinien nigdy zawierać odniesień do fragmentów, aktywności ani widoków. Prawda czy fałsz?
- Prawda
- Fałsz
Pytanie 3
Kiedy ViewModel jest niszczony?
- Gdy powiązany kontroler interfejsu jest niszczony i tworzony ponownie podczas zmiany orientacji urządzenia.
- w przypadku zmiany orientacji,
- Gdy powiązany kontroler interfejsu zakończy działanie (jeśli jest to aktywność) lub zostanie odłączony (jeśli jest to fragment).
- Gdy użytkownik naciśnie przycisk Wstecz.
Pytanie 4
Do czego służy interfejs ViewModelFactory?
- Utwórz instancję obiektu
ViewModel. - Zachowywanie danych podczas zmiany orientacji.
- Odświeżanie danych wyświetlanych na ekranie.
- otrzymywać powiadomienia, gdy dane aplikacji zostaną zmienione;
Rozpocznij kolejną lekcję:
Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z podstaw języka Kotlin na Androidzie.




