Te ćwiczenia są częścią kursu Android Kotlin Fundamentals. Skorzystaj z tego kursu, jeśli będziesz wykonywać kolejno kilka ćwiczeń z programowania. Wszystkie ćwiczenia z kursu są wymienione na stronie docelowej ćwiczeń z programowania na temat Kotlin.
Ekran tytułowy |
Ekran gry |
Ekran wyniku |
Wstęp
Dzięki ćwiczeniom z programowania poznasz jeden z komponentów architektury Androida: ViewModel
:
- Klasa
ViewModel
służy do przechowywania danych związanych z interfejsem użytkownika i zarządzania nimi w sposób uwzględniający cykl życia konta. KlasaViewModel
umożliwia przetrwanie zmian konfiguracji urządzenia, np. obrotu ekranu i zmiany dostępności klawiatury. - Aby utworzyć instancję i zwrócić obiekt
ViewModel
, który przeżyje zmiany konfiguracji, użyj klasyViewModelFactory
.
Co musisz wiedzieć
- Jak tworzyć podstawowe aplikacje na Androida w Kotlinie.
- Jak za pomocą wykresu nawigacyjnego zaimplementować nawigację w aplikacji.
- Jak dodać kod, aby poruszać się między miejscami docelowymi aplikacji i przekazywać dane między miejscami docelowymi.
- Jak działa cykl życia aktywności i fragmentu.
- Jak dodać dane logowania do aplikacji i odczytać logi przy użyciu LogCat w Android Studio.
Czego się nauczysz
- Dowiedz się, jak używać zalecanej architektury aplikacji na Androida.
- Jak korzystać z zajęć
Lifecycle
,ViewModel
iViewModelFactory
w aplikacji. - Jak zachować dane w interfejsie po wprowadzeniu zmian w konfiguracji urządzenia
- Co to jest wzór metody fabrycznej i jak go używać.
- Jak utworzyć obiekt
ViewModel
za pomocą interfejsuViewModelProvider.Factory
.
Jakie zadania wykonasz:
- Dodaj
ViewModel
do aplikacji, aby zapisywać dane aplikacji i przetrwać konfigurację. - Za pomocą
ViewModelFactory
i wzorca projektowania fabrycznego możesz utworzyć obiektViewModel
z parametrami konstruktora.
W ćwiczeniach z lekcji na lekcji 5 tworzysz aplikację GuessTheWord, zaczynając od kodu początkowego. GuessTheWord to 2-osobowa gra w stylu charades, w której gracze współpracują, aby osiągnąć najwyższy możliwy wynik.
Pierwszy gracz sprawdza słowa w aplikacji i wykonuje po kolei każdy z nich, starając się nie wyświetlać słów drugiemu graczowi. Drugi gracz próbuje odgadnąć słowo.
Aby zagrać w grę, pierwszy gracz otworzy aplikację na urządzeniu i zobaczy słowo, na przykład "gitara&", jak na zrzucie ekranu poniżej.
Gracz wypowiada słowo, pamiętając, aby nie mówić samego słowa.
- Gdy drugi gracz odgadnie słowo poprawnie, pierwszy naciśnij przycisk OK, co zwiększa liczbę o jeden i wyświetla następne słowo.
- Jeśli drugi gracz nie odgadnie słowa, pierwszy z nich naciśnie przycisk Pomiń, co zmniejszy liczbę o jeden i przeskoczy do następnego.
- Aby zakończyć grę, naciśnij przycisk Zakończ grę. (Ta funkcja nie jest w kodzie początkowym pierwszego modułu ćwiczeń z serii).
W tym zadaniu pobierzesz i uruchomisz aplikację startową oraz sprawdzisz kod.
Krok 1. Rozpocznij
- Pobierz kod startowy GuessTheWord i otwórz projekt w Android Studio.
- Uruchom aplikację na urządzeniu z Androidem lub w emulatorze.
- Kliknij przyciski. Pamiętaj, że przycisk Pomiń wyświetla następne słowo i obniża wynik o jeden, a przycisk OK pokazuje kolejne słowo i zwiększa wynik o jeden. Przycisk Zakończ grę nie jest zaimplementowany, więc nic się nie dzieje, gdy go klikniesz.
Krok 2. Wykonaj kod
- Zapoznaj się z kodem, aby dowiedzieć się, jak działa aplikacja w Android Studio.
- Pamiętaj, aby przyjrzeć się plikom opisanym poniżej, 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
przechowuje inne fragmenty, gdy użytkownik porusza się po aplikacji.
Fragmenty interfejsu
Kod startowy zawiera 3 fragmenty w 3 różnych pakietach w pakiecie com.example.android.guesstheword.screens
:
title/TitleFragment
na ekranie tytułowymgame/GameFragment
na ekran gryscore/ScoreFragment
na ekranie wyniku
ekrany/tytuł/TytułFragment.kt
Fragment tytułu to pierwszy ekran wyświetlany po uruchomieniu aplikacji. Moduł obsługi kliknięcia to przycisk Zagraj, który pozwala przejść do ekranu gry.
ekrany/gry/GameFragment.kt
To jest główny fragment, w którym większość działania ma miejsce:
- Zmienne są zdefiniowane dla bieżącego słowa i bieżącego wyniku.
wordList
zdefiniowana w metodzieresetList()
to przykładowa lista słów, których należy użyć w grze.- Metoda
onSkip()
to moduł do obsługi kliknięć przycisku Pomiń. Zmniejsza wynik o 1, a następnie wyświetla następne słowo za pomocą metodynextWord()
. - Metoda
onCorrect()
to moduł do obsługi kliknięć przycisku OK. Ta metoda jest zaimplementowana podobnie do metodyonSkip()
. Jedyna różnica polega na tym, że ta metoda dodaje wynik do wyniku zamiast odejmować.
ekrany/wynik/ScoreFragment.kt
ScoreFragment
to ostatni ekran w grze, w którym wyświetla się końcowy wynik gracza. W ćwiczeniach z programowania dodajesz implementację, aby wyświetlać ten ekran i wyświetlać końcowy wynik.
res/Nawigacja/glowna_nawigacja.xml
Wykres nawigacji pokazuje sposób łączenia fragmentów za pomocą nawigacji:
- Z fragmentu tytułu użytkownik może przejść do fragmentu gry.
- Z fragmentu z gry użytkownik może przejść do fragmentu wyniku.
- Po kliknięciu fragmentu wyniku użytkownik może wrócić do fragmentu gry.
W tym zadaniu znajdziesz problemy z aplikacją GuessTheWord Starter.
- Uruchom kod startowy i rozpocznij grę po kilku słowach, klikając Pomiń lub OK za każdym słowem.
- Na ekranie gry będzie teraz widoczne słowo i aktualny wynik. Aby zmienić orientację ekranu, obróć urządzenie lub emulator. Zwróć uwagę, że bieżący wynik został utracony.
- Uruchom grę przez kilka słów. Gdy na ekranie pojawi się pewien wynik, zamknij i ponownie otwórz aplikację. Pamiętaj, że gra uruchamia się od początku, ponieważ stan aplikacji nie jest zapisany.
- Graj w kilka słów, a następnie kliknij przycisk Zakończ grę. Zwróć uwagę, że nic się nie dzieje.
Problemy z aplikacją:
- Aplikacja startowa nie zapisuje i nie przywraca stanu aplikacji podczas zmiany konfiguracji, na przykład w przypadku zmiany orientacji urządzenia lub po wyłączeniu i ponownym uruchomieniu aplikacji.
Możesz rozwiązać ten problem za pomocą wywołania zwrotnegoonSaveInstanceState()
. Użycie metodyonSaveInstanceState()
wymaga jednak napisania dodatkowego kodu, który pozwoli zapisać stan w pakiecie i wdrożyć logikę, by pobrać ten stan. Ponadto jest możliwość zminimalizowania ilości przechowywanych danych. - Gdy użytkownik kliknie przycisk Zakończ grę, nie przejdzie na ekran wyników.
Problemy te znajdziesz w komponentach architektury aplikacji, które omawiasz w ćwiczeniach z programowania.
Architektura aplikacji
Architektura aplikacji to sposób projektowania aplikacji, klas i relacji między nimi. Dzięki temu kod jest zorganizowany, działa dobrze w określonych sytuacjach i jest łatwy w pracy. W tym zestawie czterech ćwiczeń z programowania poprawki wprowadzone w aplikacji GuessTheWord są zgodne ze wskazówkami dotyczącymi architektury aplikacji na Androida i używasz komponentów architektury Androida. Architektura aplikacji na Androida jest podobna do wzorca MVVM (model-view-viewmodel).
Aplikacja GuessTheWord jest zgodna z zasadą oddzielania obaw i jest podzielona na klasy, a każda z nich zawiera osobne zagadnienia. W pierwszym ćwiczeniu z ćwiczeń, z którymi współpracujesz, będą kontrolery interfejsu, ViewModel
i ViewModelFactory
.
Kontroler interfejsu
Kontroler interfejsu to klasa interfejsu użytkownika, na przykład Activity
lub Fragment
. Kontroler interfejsu powinien zawierać wyłącznie logikę obsługującą interfejs użytkownika i interakcje z systemem operacyjnym, takie jak wyświetlanie widoków i przechwytywanie danych wejściowych użytkownika. Nie stosuj zasad decyzyjnych, takich jak logika decydująca o wyświetlaniu tekstu w kontrolerze interfejsu.
W kodzie początkowym GuessTheWord kontrolery interfejsu użytkownika składają się z 3 fragmentów: GameFragment
, ScoreFragment,
i TitleFragment
. Zgodnie z zasadą „rozdzielania obaw” GameFragment
ma za zadanie jedynie rysować elementy gry na ekranie i dowiedzieć się, kiedy użytkownik klika przyciski, a także wiele innych. Gdy użytkownik kliknie przycisk, te informacje zostaną przekazane do: GameViewModel
.
Wyświetl model
ViewModel
zawiera dane do wyświetlenia we fragmencie lub działaniu powiązanym z elementem ViewModel
. ViewModel
może wykonywać proste obliczenia i przekształcać dane, 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ż dane wyświetlają się na ekranie. GameViewModel
zawiera też logikę biznesową, która pozwala przeprowadzić proste obliczenia, aby określić bieżący stan danych.
WidokModelFactory
ViewModelFactory
tworzy instancje ViewModel
z parametrami konstruktora lub bez nich.
W kolejnych ćwiczeniach z programowania poznasz inne komponenty architektury Androida, które są powiązane z kontrolerami interfejsu i usługą ViewModel
.
Klasa ViewModel
służy do przechowywania danych związanych z interfejsem użytkownika i zarządzania nimi. W tej aplikacji każdy element ViewModel
jest powiązany z jednym fragmentem.
W tym zadaniu dodasz pierwszy ViewModel
do swojej aplikacji GameViewModel
na GameFragment
. Dowiesz się też, co oznacza, że ViewModel
jest zależny od cyklu życia.
Krok 1. Dodaj klasę GameViewModel
- Otwórz plik
build.gradle(module:app)
. W blokudependencies
dodaj zależność Gradle do elementuViewModel
.
Jeśli używasz najnowszej wersji biblioteki, aplikacja w zakresie rozwiązań powinna się skompilować zgodnie z oczekiwaniami. Jeśli tak się nie stanie, spróbuj rozwiązać problem lub przywróć podaną poniżej wersję.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
- W folderze
screens/game/
utwórz nową klasę Kotlin o nazwieGameViewModel
. - Ustaw jako klasę
GameViewModel
abstrakcyjnąViewModel
. - Aby ułatwić zrozumienie, w jaki sposób tag
ViewModel
jest zależny od cyklu życia, dodaj blokinit
za pomocą instrukcjilog
.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}
Krok 2: Zastąp funkcję ClearClear() i dodaj logowanie
Element ViewModel
jest niszczony po odłączeniu powiązanego fragmentu lub po zakończeniu działania. Tuż przed zniszczeniem elementu ViewModel
wywoływane jest wywołanie zwrotne onCleared()
, by wyczyścić zasoby.
- W klasie
GameViewModel
zastąp metodęonCleared()
. - Dodaj instrukcję logowania w obrębie
onCleared()
, aby śledzić cykl życia elementuGameViewModel
.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
Krok 3. Powiąż GameGameModel z fragmentem z gry
Obiekt ViewModel
musi być powiązany z kontrolerem interfejsu. Aby powiązać oba te elementy, utwórz odwołanie do ViewModel
w kontrolerze interfejsu.
W tym kroku tworzysz odwołanie do GameViewModel
w ramach odpowiedniego kontrolera interfejsu, czyli GameFragment
.
- W klasie
GameFragment
dodaj pole typuGameViewModel
na najwyższym poziomie jako zmienną klasy.
private lateinit var viewModel: GameViewModel
Krok 4. Zainicjuj widok danych
Podczas zmiany konfiguracji, np. rotacji ekranu, kontrolery interfejsu (np. fragmenty) są tworzone ponownie. Instancje ViewModel
działają jednak dalej. Jeśli utworzysz instancję ViewModel
za pomocą klasy ViewModel
, po każdym jej utworzeniu będzie tworzony nowy obiekt. Zamiast tego utwórz instancję ViewModel
za pomocą ViewModelProvider
.
Jak działa ViewModelProvider
:
ViewModelProvider
zwraca wartośćViewModel
, jeśli już istnieje, lub tworzy nową, jeśli jeszcze nie istnieje.ViewModelProvider
tworzy instancjęViewModel
powiązaną z określonym zakresem (aktywności lub fragmentu).- Utworzony
ViewModel
jest przechowywany, dopóki zakres jest aktywny. Jeśli na przykład zakres jest fragmentem, elementViewModel
jest przechowywany, dopóki fragment nie zostanie odłączony.
Zainicjuj metodę ViewModel
za pomocą metody ViewModelProviders.of()
, aby utworzyć ViewModelProvider
:
- W klasie
GameFragment
zainicjuj zmiennąviewModel
. Umieść ten kod w obrębie znacznikówonCreateView()
po definicji zmiennej wiązania. Użyj metodyViewModelProviders.of()
i przekaż powiązany kontekstGameFragment
i klasęGameViewModel
. - Nad inicjacją obiektu
ViewModel
dodaj instrukcję logu, by 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 użyj filtra
Game
. Kliknij przycisk Odtwórz na urządzeniu lub w emulatorze. Otworzy się ekran gry.
Jak widać w LogCat, metodaonCreateView()
metodyGameFragment
wywołuje metodęViewModelProviders.of()
, by utworzyćGameViewModel
. Wyciągi dodane przez Ciebie doGameFragment
iGameViewModel
pojawią się w Logcat.
- Włącz ustawienie autoobracania w urządzeniu lub emulatorze i kilka razy zmień orientację ekranu.
GameFragment
jest niszczony i odtwarzany za każdym razem, więc wywołanieViewModelProviders.of()
jest wywoływane za każdym razem.GameViewModel
jest tworzony tylko raz i nie jest tworzona ani niszczona 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
- Wyjdź z gry lub pokaż jej fragment.
GameFragment
jest zniszczony. Powiązana jest teżGameViewModel
, a wywoływane jest wywołanie zwrotneonCleared()
.
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!
Plik ViewModel
pokonuje zmiany konfiguracji, dlatego jest dobrym rozwiązaniem w przypadku danych, które muszą przetrwać zmiany konfiguracji:
- Umieść dane do wyświetlania na ekranie i kod, aby je przetworzyć w narzędziu
ViewModel
. - Element
ViewModel
nie powinien nigdy zawierać odniesień do fragmentów, aktywności ani widoków danych, ponieważ aktywności, fragmenty i widoki danych nie przetrwają ze zmianą konfiguracji.
Dla porównania przyjrzyjmy się, jak dane GameFragment
UI są obsługiwane w aplikacji startowej przed dodaniem ViewModel
i po dodaniu ViewModel
:
- Zanim dodasz
ViewModel
:
gdy aplikacja zmieni konfigurację (np. obrót ekranu), fragment gry zostanie zniszczony i odtworzony ponownie. Dane zostaną utracone. - Po dodaniu elementu
ViewModel
i przeniesieniu fragmentu interfejsu gry do interfejsuViewModel
:
wszystkie dane, które mają być widoczne w tym fragmencie, to terazViewModel
. Po wprowadzeniu zmiany w aplikacjiViewModel
przetrwa, a dane zostaną zachowane.
W tym zadaniu przeniesiesz dane z interfejsu aplikacji do klasy GameViewModel
wraz z metodami przetwarzania danych. W ten sposób dane zostaną zachowane podczas zmian konfiguracji.
Krok 1. Przenieś pola danych i przetwarzanie danych do elementu ModelModel
Przenieś następujące pola i metody danych z GameFragment
do GameViewModel
:
- Przenieś pola danych
word
,score
iwordList
. Upewnij się, żeword
iscore
to nieprivate
.
Nie przenoś zmiennej wiązaniaGameFragmentBinding
, ponieważ zawiera odniesienia do widoków. Ta zmienna służy do zwiększania układu, konfigurowania detektorów kliknięć i wyświetlania danych na ekranie – obowiązków fragmentu. - Przenieś metody
resetList()
inextWord()
. Te metody określają, jakie słowo ma być wyświetlane na ekranie. - W ramach metody
onCreateView()
przenieś wywołania metody doresetList()
inextWord()
do blokuinit
elementuGameViewModel
.
Te metody muszą znajdować się w blokuinit
, ponieważ lista słów powinna być resetowana podczas tworzeniaViewModel
, a nie za każdym razem, gdy fragment jest tworzony. Możesz usunąć instrukcję dziennika w blokuinit
o wartościGameFragment
.
Moduły obsługi kliknięć onSkip()
i onCorrect()
w GameFragment
zawierają kod do przetwarzania danych i aktualizowania interfejsu użytkownika. Kod do aktualizowania interfejsu powinien pozostać we fragmencie, ale kod przetwarzania danych musi zostać przeniesiony do ViewModel
.
Na razie umieść identyczne metody w obu miejscach:
- Skopiuj metody
onSkip()
ionCorrect()
zGameFragment
doGameViewModel
. - W tagu
GameViewModel
sprawdź, czy metodyonSkip()
ionCorrect()
nie sąprivate
, ponieważ odwołasz się do tych metod z fragmentu.
Oto kod zajęć 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 modułów obsługi kliknięć i pól danych w GameFragment
- W
GameFragment
zaktualizuj metodyonSkip()
ionCorrect()
. Usuń kod, aby zaktualizować wynik i wywołać odpowiednie metodyonSkip()
ionCorrect()
wviewModel
. - Metoda
nextWord()
została przeniesiona do właściwościViewModel
, dlatego fragment kodu gry nie ma już do niego dostępu.
W metodzieGameFragment
w metodachonSkip()
ionCorrect()
zastąp wywołanienextWord()
elementamiupdateScoreText()
iupdateWordText()
. Te metody wyświetlają dane na ekranie.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}
- W narzędziu
GameFragment
zaktualizuj zmiennescore
iword
, by korzystały ze zmiennychGameViewModel
, ponieważ zmienne znajdują się teraz w sekcjiGameViewModel
.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}
- W elemencie
GameViewModel
wewnątrz metodynextWord()
usuń wywołania metodyupdateWordText()
iupdateScoreText()
. Metody te są teraz wywoływane z elementuGameFragment
. - Utwórz aplikację i upewnij się, że nie ma w niej żadnych błędów. Jeśli wystąpiły błędy, wyczyść projekt i utwórz go od nowa.
- Uruchom aplikację i zagraj w grę za pomocą kilku słów. Będąc na ekranie gry, obróć urządzenie. Zwróć uwagę, że zmiana wyniku i bieżącego słowa zostaną zachowane.
Brawo! Teraz wszystkie dane aplikacji są przechowywane w narzędziu ViewModel
, więc są przechowywane w trakcie zmian konfiguracji.
W tym zadaniu implementujesz detektor kliknięć przycisku Zakończ grę.
- W
GameFragment
dodaj metodęonEndGame()
. MetodaonEndGame()
zostanie wywołana, gdy użytkownik kliknie przycisk Zakończ grę.
private fun onEndGame() {
}
- W metodzie
GameFragment
znajdź w metodzieonCreateView()
kod, który ustawia odbiorniki dla przycisków OK i Pomiń. Pod tymi wierszami ustaw detektor kliknięć przycisku Zakończ grę. Użyj zmiennej wiązaniabinding
. W odbiorniku kliknięć wywołaj metodęonEndGame()
.
binding.endGameButton.setOnClickListener { onEndGame() }
- W
GameFragment
dodaj metodę o nazwiegameFinished()
, aby poruszać się po aplikacji na ekranie z wynikiem. Przekaż wynik jako argument za pomocą funkcji 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 przełączaj się między słowami. Kliknij przycisk Zakończ grę. Aplikacja wyświetli ekran z wynikiem, ale ostateczny wynik nie zostanie wyświetlony. W następnym zadaniu naprawisz to.
Gdy użytkownik zakończy grę, ScoreFragment
nie wyświetla wyniku. Chcesz, aby ScoreFragment
wyświetlał wynik w ViewModel
. Podczas inicjowania funkcji ViewModel
przekażesz wartość wyniku za pomocą wzorca metody fabrycznej.
Wzór metody fabrycznej to wzór konstrukcyjny, który wykorzystuje metody fabryczne do tworzenia obiektów. Metoda fabryczna to metoda zwracająca wystąpienie tej samej klasy.
W tym zadaniu tworzysz ViewModel
z parametrowym konstruktorem dla fragmentu wyniku i metodą fabryczną, aby utworzyć instancję ViewModel
.
- W pakiecie
score
utwórz nową klasę Kotlin o nazwieScoreViewModel
. Te fragmenty kodu będą zawieraćViewModel
we fragmencie wyniku. - Wydłuż klasę
ScoreViewModel
zViewModel.
Dodaj parametr konstruktora do ostatecznego wyniku. Dodaj blokinit
z deklaracją logu. - W klasie
ScoreViewModel
dodaj 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
score
utwórz kolejną klasę Kotlin o nazwieScoreViewModelFactory
. Ta klasa będzie odpowiadać za tworzenie instancjiScoreViewModel
. - Przedłuż klasę
ScoreViewModelFactory
zViewModelProvider.Factory
. Dodaj parametr konstruktora do wyniku końcowego.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
- W
ScoreViewModelFactory
Studio Android wyświetla błąd o niezaimplementowanym elemencie abstrakcyjnym. Aby naprawić błąd, zastąp metodęcreate()
. Metodacreate()
zwraca 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
ScoreFragment
utwórz zmienne klasScoreViewModel
iScoreViewModelFactory
.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
- W
ScoreFragment
w elemencieonCreateView()
zainicjuj zmiennąbinding
, inicjujviewModelFactory
. UżyjScoreViewModelFactory
. Przekaż końcowy wynik z pakietu argumentów jako parametr konstruktora do elementuScoreViewModelFactory()
.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
- W
onCreateView(
) po zainicjowaniuviewModelFactory
zainicjuj obiektviewModel
. Wywołaj metodęViewModelProviders.of()
, przekaż kontekst powiązanego fragmentu wyniku iviewModelFactory
. Spowoduje to utworzenie obiektuScoreViewModel
przy użyciu metody fabrycznej określonej w klasieviewModelFactory
.
.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)
- W metodzie
onCreateView()
po zainicjowaniuviewModel
ustaw tekst widokuscoreText
na końcowy wynik zdefiniowany wScoreViewModel
.
binding.scoreText.text = viewModel.score.toString()
- Uruchom aplikację i zagraj w grę. Przełączaj się między słowami lub wszystkimi słowami i kliknij Zakończ grę. Zwróć uwagę, że fragment wyniku zawiera teraz ostateczny wynik.
- Opcjonalnie: sprawdź logi
ScoreViewModel
w narzędziu LogCat, filtrując je według wartościScoreViewModel
. Wartość wyniku powinna być wyświetlana.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
W tym zadaniu zaimplementowałeś ScoreFragment
, aby używać ViewModel
. Wiesz też, jak za pomocą interfejsu ViewModelFactory
utworzyć konstruktor z parametrami dla ViewModel
.
Gratulacje! Zmieniłeś architekturę aplikacji, aby używać jednego z komponentów architektury Androida: ViewModel
. Rozwiązano problem z cyklem życia aplikacji i teraz dane pozostają w stanie po zmianie konfiguracji. Wiesz też, jak utworzyć konstruktor z parametrami do tworzenia obiektu ViewModel
za pomocą interfejsu ViewModelFactory
.
Projekt na Android Studio: GuessTheWord
- Wytyczne dotyczące architektury aplikacji na Androida zaleca się oddzielać zajęcia, które pełnią różne obowiązki.
- Kontroler interfejsu to klasa interfejsu, na przykład
Activity
lubFragment
. Kontrolery interfejsu użytkownika powinny zawierać wyłącznie funkcje umożliwiające obsługę interakcji użytkownika i systemu operacyjnego. Nie powinny zawierać danych do wyświetlania w interfejsie. Umieść te dane w narzędziuViewModel
. - Klasa
ViewModel
przechowuje dane związane z interfejsem użytkownika i nimi zarządza. KlasaViewModel
umożliwia przetrwanie zmian konfiguracji, takich jak obrót ekranu. ViewModel
to jeden z zalecanych komponentów architektury Androida.ViewModelProvider.Factory
to interfejs, którego można użyć do utworzenia obiektuViewModel
.
Tabela poniżej zawiera porównanie kontrolerów interfejsu z instancjami ViewModel
, które zawierają dla nich dane:
Kontroler interfejsu | WidokModel |
Przykładowym kontrolerem interfejsu jest | Przykład: |
Nie zawiera żadnych danych do wyświetlenia w interfejsie użytkownika. | Zawiera dane, które kontroler interfejsu wyświetla w interfejsie. |
Zawiera kod do wyświetlania danych oraz kod zdarzenia użytkownika, np. detektor kliknięć. | Zawiera kod do przetwarzania danych. |
Zniszczona i odtwarzana podczas każdej zmiany konfiguracji. | Jest zniszczone tylko wtedy, gdy powiązany kontroler interfejsu zniknie na stałe – w przypadku aktywności, po zakończeniu aktywności lub w przypadku jej odłączenia. |
Zawiera widoki. | Element nie powinien nigdy zawierać odwołań do aktywności, fragmentów ani widoków danych, ponieważ nie przetrwają one ze zmianą konfiguracji, ale tag |
Zawiera odwołanie do powiązanego elementu | Nie zawiera żadnego odniesienia do powiązanego kontrolera interfejsu. |
Kurs Udacity:
Dokumentacja dla programistów Androida:
- Omówienie modelu Model
- Obsługa cykli życia z komponentami zależnymi od cyklu życia
- Przewodnik po architekturze aplikacji
ViewModelProvider
ViewModelProvider.Factory
Inne:
- Wzorzec architektoniczny MVVM (model-view-viewmodel).
- Zasada rozdziału obaw
- Wzorzec metody fabrycznej
Ta sekcja zawiera listę możliwych zadań domowych dla uczniów, którzy pracują w ramach tego ćwiczenia w ramach kursu prowadzonego przez nauczyciela. To nauczyciel może wykonać te czynności:
- W razie potrzeby przypisz zadanie domowe.
- Poinformuj uczniów, jak przesyłać zadania domowe.
- Oceń projekty domowe.
Nauczyciele mogą wykorzystać te sugestie tak długo, jak chcą lub chcą, i mogą przypisać dowolne zadanie domowe.
Jeśli samodzielnie wykonujesz te ćwiczenia z programowania, możesz sprawdzić swoją wiedzę w tych zadaniach domowych.
Odpowiedz na te pytania
Pytanie 1
Wybierz klasę, aby zapisać dane aplikacji. Pozwoli to uniknąć utraty danych podczas zmiany konfiguracji urządzenia.
ViewModel
LiveData
Fragment
Activity
Pytanie 2
Element ViewModel
nie powinien nigdy zawierać żadnych odniesień do fragmentów, aktywności ani widoków danych. Prawda czy fałsz?
- Prawda
- Fałsz
Pytanie 3
Kiedy nastąpi ViewModel
?
- Gdy powiązany kontroler interfejsu użytkownika zostanie zniszczony i odtworzony ponownie w wyniku zmiany orientacji urządzenia.
- w zmianie orientacji.
- Po zakończeniu działania powiązanego kontrolera (jeśli jest to działanie) lub odłączonym (jeśli jest to fragment).
- Gdy użytkownik naciśnie przycisk Wstecz.
Pytanie 4
Do czego służy interfejs ViewModelFactory
?
- Tworzę instancję obiektu
ViewModel
. - Przechowywanie danych podczas zmiany orientacji.
- Odświeżam dane wyświetlane na ekranie.
- otrzymywanie powiadomień o zmianie danych aplikacji.
Rozpocznij następną lekcję:
Linki do innych ćwiczeń z programowania w tym kursie znajdziesz na stronie docelowej z ćwiczeniami z podstaw Androida Kotlin.