Android Kotlin Fundamentals 05.2: LiveData i obserwatorzy LiveData

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.

Wprowadzenie

W poprzednim laboratorium kodowania w aplikacji GuessTheWord użyto ViewModel, aby dane aplikacji przetrwały zmiany konfiguracji urządzenia. Z tego modułu dowiesz się, jak zintegrować LiveData z danymi w klasach ViewModel. LiveData, który jest jednym z komponentów architektury Androida, umożliwia tworzenie obiektów danych, które powiadamiają widoki o zmianach w bazie danych.

Aby użyć klasy LiveData, musisz skonfigurować „obserwatorów” (np. działania lub fragmenty), którzy będą śledzić zmiany w danych aplikacji. LiveData jest świadomy cyklu życia, więc aktualizuje tylko obserwatorów komponentów aplikacji, którzy są w aktywnym stanie cyklu życia.

Co warto wiedzieć

  • Jak tworzyć podstawowe aplikacje na Androida w języku Kotlin.
  • Jak poruszać się między miejscami docelowymi w aplikacji.
  • Cykl życia działania i fragmentu.
  • Jak używać obiektów ViewModel w aplikacji.
  • Jak tworzyć obiekty ViewModel za pomocą interfejsu ViewModelProvider.Factory.

Czego się nauczysz

  • Co sprawia, że LiveData obiektów jest przydatnych.
  • Jak dodać LiveData do danych przechowywanych w ViewModel.
  • Kiedy i jak używać MutableLiveData.
  • Jak dodać metody obserwatora, aby obserwować zmiany w LiveData.
  • Jak enkapsulować LiveData za pomocą właściwości pomocniczej.
  • Jak komunikować się między kontrolerem interfejsu a odpowiadającym mu elementem ViewModel.

Jakie zadania wykonasz

  • Użyj LiveData dla słowa i wyniku w aplikacji GuessTheWord.
  • Dodaj obserwatorów, którzy będą reagować na zmianę słowa lub wyniku.
  • Aktualizuj widoki tekstu, które wyświetlają zmienione wartości.
  • Użyj wzorca LiveData obserwatora, aby dodać zdarzenie zakończenia gry.
  • Zaimplementuj przycisk Zagraj jeszcze raz.

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 laboratorium nauczysz się ulepszać aplikację GuessTheWord, dodając zdarzenie, które kończy grę, gdy użytkownik przejdzie przez wszystkie słowa w aplikacji. Dodasz też przycisk Zagraj ponownie w fragmencie wyniku, aby użytkownik mógł ponownie zagrać w grę.

Ekran tytułowy

Ekran gry

Ekran wyników

W tym zadaniu znajdziesz i uruchomisz kod początkowy do tego ćwiczenia z programowania. Możesz użyć aplikacji GuessTheWord utworzonej w poprzednim laboratorium, lub pobrać aplikację startową.

  1. (Opcjonalnie) Jeśli nie używasz kodu z poprzednich ćwiczeń z programowania, pobierz kod początkowy do tych ćwiczeń. Rozpakuj kod i otwórz projekt w Android Studio.
  2. Uruchom aplikację i zagraj w grę.
  3. 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ę kończy grę.

LiveData to klasa przechowująca dane, które można obserwować, i która uwzględnia cykl życia. Możesz na przykład umieścić element LiveData wokół aktualnego wyniku w aplikacji GuessTheWord. W tym laboratorium dowiesz się więcej o kilku cechach elementu LiveData:

  • LiveData jest obserwowalny, co oznacza, że obserwator otrzymuje powiadomienie, gdy dane przechowywane przez obiekt LiveData ulegną zmianie.
  • LiveData przechowuje dane; LiveData to otoczka, której można używać z dowolnymi danymi.
  • LiveData uwzględnia cykl życia, co oznacza, że aktualizuje tylko obserwatorów, którzy są w aktywnym stanie cyklu życia, np. STARTED lub RESUMED.

W tym zadaniu dowiesz się, jak opakować dowolny typ danych w obiekty LiveData, przekształcając bieżące dane o wyniku i bieżące dane o słowie w GameViewModel na LiveData. W późniejszym zadaniu dodasz do tych obiektów LiveData obserwatora i dowiesz się, jak obserwować LiveData.

Krok 1. Zmień wynik i słowo, aby używać LiveData

  1. W pakiecie screens/game otwórz plik GameViewModel.
  2. Zmień typ zmiennych scoreword na MutableLiveData.

    MutableLiveData to LiveData, którego wartość można zmienić. MutableLiveData to klasa ogólna, więc musisz określić typ danych, które zawiera.
// The current word
val word = MutableLiveData<String>()
// The current score
val score = MutableLiveData<Int>()
  1. W pliku GameViewModel w bloku init zainicjuj zmienne scoreword. Aby zmienić wartość zmiennej LiveData, użyj metody setValue(). W języku Kotlin możesz wywołać funkcję setValue() za pomocą właściwości value.
init {

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

Krok 2. Zaktualizuj odwołanie do obiektu LiveData

Zmienne scoreword mają teraz typ LiveData. W tym kroku zmienisz odwołania do tych zmiennych, używając właściwości value.

  1. GameViewModel w metodzie onSkip() zmień score na score.value. Zwróć uwagę na błąd dotyczący tego, że score może być null. Ten błąd naprawisz w następnym kroku.
  2. Aby rozwiązać ten problem, dodaj znak null do score.valueonSkip(). Następnie wywołaj funkcję minus() na score, która wykonuje odejmowanie z null-bezpieczeństwem.
fun onSkip() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.minus(1)
   }
   nextWord()
}
  1. Zaktualizuj metodę onCorrect() w ten sam sposób: dodaj sprawdzenie null do zmiennej score i użyj funkcji plus().
fun onCorrect() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.plus(1)
   }
   nextWord()
}
  1. GameViewModel w metodzie nextWord() zmień odwołanie word na word.value.
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       word.value = wordList.removeAt(0)
   }
}
  1. GameFragment w metodzie updateWordText() zmień odwołanie do viewModel.word na viewModel.word.value..
/** Methods for updating the UI **/
private fun updateWordText() {
   binding.wordText.text = viewModel.word.value
}
  1. W funkcji GameFragment w metodzie updateScoreText() zmień odwołanie do viewModel.score na viewModel.score.value..
private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.value.toString()
}
  1. W pliku GameFragment w metodzie gameFinished() zmień odwołanie do viewModel.score na viewModel.score.value. Dodaj wymagane null-safety check.
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score.value?:0
   NavHostFragment.findNavController(this).navigate(action)
}
  1. Sprawdź, czy w kodzie nie ma błędów. Skompiluj i uruchom aplikację. Jej funkcje powinny być takie same jak wcześniej.

To zadanie jest ściśle powiązane z poprzednim, w którym przekonwertowano dane dotyczące wyników i słów na obiekty LiveData. W tym zadaniu do obiektów LiveData dołączysz obiekty Observer.

  1. GameFragment, w metodzie onCreateView() dołącz obiekt Observer do obiektu LiveData dla bieżącego wyniku, viewModel.score. Użyj metody observe() i umieść kod po inicjowaniu viewModel. Użyj wyrażenia lambda, aby uprościć kod. (Wyrażenie lambda to funkcja anonimowa, która nie jest deklarowana, ale jest przekazywana natychmiast jako wyrażenie).
viewModel.score.observe(this, Observer { newScore ->
})

Rozwiąż problem z odwołaniem do Observer. Aby to zrobić, kliknij Observer, naciśnij Alt+Enter (Option+Enter na komputerze Mac) i zaimportuj androidx.lifecycle.Observer.

  1. Obserwator, którego właśnie utworzono, otrzymuje zdarzenie, gdy zmienią się dane przechowywane przez obserwowany obiekt LiveData. W obserwatorze zaktualizuj wynik TextView, wpisując nowy wynik.
/** Setting up LiveData observation relationship **/
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. Dołącz obiekt Observer do obiektu LiveData bieżącego słowa. Zrób to w taki sam sposób, w jaki dołączyłeś(-aś) obiekt Observer do bieżącego wyniku.
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
   binding.wordText.text = newWord
})

Gdy zmieni się wartość score lub word, wyświetlane na ekranie score lub word zostaną automatycznie zaktualizowane.

  1. GameFragment usuń metody updateWordText()updateScoreText() oraz wszystkie odwołania do nich. Nie są już potrzebne, ponieważ widoki tekstu są aktualizowane przez metody obserwatora LiveData.
  2. Uruchom aplikację. Gra powinna działać tak samo jak wcześniej, ale teraz korzysta z obserwatorów LiveDataLiveData.

Hermetyzacja to sposób na ograniczenie bezpośredniego dostępu do niektórych pól obiektu. Gdy enkapsulujesz obiekt, udostępniasz zestaw publicznych metod, które modyfikują prywatne pola wewnętrzne. Dzięki hermetyzacji możesz kontrolować, jak inne klasy manipulują tymi polami wewnętrznymi.

W bieżącym kodzie każda klasa zewnętrzna może modyfikować zmienne scoreword za pomocą właściwości value, np. używając viewModel.score.value. W aplikacji, którą tworzysz w tym laboratorium, może to nie mieć znaczenia, ale w aplikacji produkcyjnej chcesz mieć kontrolę nad danymi w obiektach ViewModel.

Tylko ViewModel powinna edytować dane w aplikacji. Kontrolery interfejsu użytkownika muszą jednak odczytywać dane, więc pola danych nie mogą być całkowicie prywatne. Do hermetyzacji danych aplikacji używasz obiektów MutableLiveDataLiveData.

MutableLiveData – LiveData:

  • Dane w obiekcie MutableLiveData można zmieniać, jak sama nazwa wskazuje. Wewnątrz ViewModel dane powinny być edytowalne, więc używa się MutableLiveData.
  • Dane w obiekcie LiveData można odczytywać, ale nie można ich zmieniać. Dane spoza ViewModel powinny być dostępne do odczytu, ale nie do edycji, dlatego powinny być udostępniane jako LiveData.

Aby zrealizować tę strategię, użyj właściwości pomocniczej w języku Kotlin. Właściwość pomocnicza umożliwia zwrócenie z funkcji pobierającej czegoś innego niż dokładny obiekt. W tym zadaniu zaimplementujesz właściwość pomocniczą dla obiektów score i word w aplikacji GuessTheWord.

Dodawanie właściwości pomocniczej do wyniku i słowa

  1. W GameViewModel ustaw bieżący obiekt score jako private.
  2. Aby zachować konwencję nazewnictwa stosowaną we właściwościach pomocniczych, zmień score na _score. Właściwość _score jest teraz wersją wyniku gry, którą można zmieniać i która jest używana wewnętrznie.
  3. Utwórz publiczną wersję typu LiveData o nazwie score.
// The current score
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
  1. Wyświetla się błąd inicjowania. Ten błąd występuje, ponieważ wewnątrz GameFragment element score jest odwołaniem do LiveData, a score nie ma już dostępu do jego funkcji ustawiającej. Więcej informacji o getterach i setterach w Kotlinie znajdziesz w artykule Getters and Setters (Gettery i settery).

    Aby rozwiązać ten problem, zastąp metodę get() dla obiektu scoreGameViewModel i zwróć właściwość pomocniczą _score.
val score: LiveData<Int>
   get() = _score
  1. GameViewModel zmień odwołania do score na jego wewnętrzną wersję modyfikowalną, czyli _score.
init {
   ...
   _score.value = 0
   ...
}

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

fun onCorrect() {
   if (!wordList.isEmpty()) {
       _score.value = (score.value)?.plus(1)
   }
   ...
}
  1. Zmień nazwę obiektu word na _word i dodaj do niego właściwość pomocniczą, tak jak w przypadku obiektu score.
// The current word
private val _word = MutableLiveData<String>()
val word: LiveData<String>
   get() = _word
...
init {
   _word.value = ""
   ...
}
...
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       _word.value = wordList.removeAt(0)
   }
}

Świetna robota! Udało Ci się zamknąć w ramce LiveData obiekty wordscore.

Obecna aplikacja przechodzi do ekranu z wynikami, gdy użytkownik kliknie przycisk Zakończ grę. Chcesz też, aby po przejściu przez wszystkie słowa aplikacja przenosiła graczy na ekran z wynikami. Po tym, jak gracze skończą z ostatnim słowem, gra powinna zakończyć się automatycznie, aby użytkownik nie musiał klikać przycisku.

Aby wdrożyć tę funkcję, musisz wywołać zdarzenie i przekazać je do fragmentu z ViewModel, gdy wyświetlą się wszystkie słowa. W tym celu użyj wzorca obserwatora LiveData, aby modelować zdarzenie zakończenia gry.

Wzorzec obserwatora

Wzorzec obserwatora to wzorzec projektowy oprogramowania. Określa komunikację między obiektami: obserwowanym (czyli „obiektem” obserwacji) i obserwatorami. Obserwowany obiekt to obiekt, który powiadamia obserwatorów o zmianach w swoim stanie.

W przypadku LiveData w tej aplikacji obserwowanym obiektem (podmiotem) jest obiekt LiveData, a obserwatorami są metody w kontrolerach interfejsu, np. fragmenty. Zmiana stanu następuje za każdym razem, gdy zmieniają się dane zawarte w LiveData. Klasy LiveData są kluczowe w komunikacji z ViewModel do fragmentu.

Krok 1. Użyj LiveData do wykrywania zdarzenia zakończenia gry

W tym zadaniu użyjesz wzorca obserwatora LiveData do modelowania zdarzenia zakończenia gry.

  1. GameViewModel utwórz obiekt Boolean MutableLiveData o nazwie _eventGameFinish. Ten obiekt będzie zawierać zdarzenie zakończenia gry.
  2. Po zainicjowaniu obiektu _eventGameFinish utwórz i zainicjuj właściwość pomocniczą o nazwie eventGameFinish.
// Event which triggers the end of the game
private val _eventGameFinish = MutableLiveData<Boolean>()
val eventGameFinish: LiveData<Boolean>
   get() = _eventGameFinish
  1. GameViewModel dodaj metodę onGameFinish(). W metodzie ustaw zdarzenie zakończenia gry, eventGameFinish, na true.
/** Method for the game completed event **/
fun onGameFinish() {
   _eventGameFinish.value = true
}
  1. GameViewModel w metodzie nextWord() zakończ grę, jeśli lista słów jest pusta.
private fun nextWord() {
   if (wordList.isEmpty()) {
       onGameFinish()
   } else {
       //Select and remove a _word from the list
       _word.value = wordList.removeAt(0)
   }
}
  1. GameFragmentonCreateView() po zainicjowaniu viewModel dołącz obserwatora do eventGameFinish. używaj metody observe(). W funkcji lambda wywołaj metodę gameFinished().
// Observer for the Game finished event
viewModel.eventGameFinish.observe(this, Observer<Boolean> { hasFinished ->
   if (hasFinished) gameFinished()
})
  1. Uruchom aplikację, zagraj w grę i przejdź przez wszystkie słowa. Aplikacja automatycznie przechodzi do ekranu z wynikami, zamiast pozostawać w fragmencie gry, dopóki nie klikniesz Zakończ grę.

    Gdy lista słów jest pusta, ustawiana jest wartość eventGameFinish, wywoływana jest powiązana metoda obserwatora we fragmencie gry, a aplikacja przechodzi do fragmentu ekranu.
  2. Dodany kod spowodował problem z cyklem życia. Aby zrozumieć problem, w klasie GameFragment zmień w komentarz kod nawigacji w metodzie gameFinished(). Pamiętaj, aby w metodzie zachować komunikat Toast.
private fun gameFinished() {
       Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
//        val action = GameFragmentDirections.actionGameToScore()
//        action.score = viewModel.score.value?:0
//        NavHostFragment.findNavController(this).navigate(action)
   }
  1. Uruchom aplikację, zagraj w grę i przejdź przez wszystkie słowa. U dołu ekranu gry na krótko pojawi się komunikat „Gra właśnie się skończyła”, co jest oczekiwanym zachowaniem.

Teraz obróć urządzenie lub emulator. Toast wyświetli się ponownie. Obróć urządzenie jeszcze kilka razy, a powiadomienie prawdopodobnie będzie się wyświetlać za każdym razem. To błąd, ponieważ powiadomienie powinno wyświetlać się tylko raz, po zakończeniu gry. Toast nie powinien być wyświetlany za każdym razem, gdy fragment jest ponownie tworzony. Rozwiążesz ten problem w kolejnym zadaniu.

Krok 2. Zresetuj zdarzenie zakończenia gry

Zwykle LiveData dostarcza aktualizacje do obserwatorów tylko wtedy, gdy dane się zmienią. Wyjątkiem od tego zachowania jest to, że obserwatorzy otrzymują też aktualizacje, gdy zmieniają stan z nieaktywnego na aktywny.

Dlatego w aplikacji wielokrotnie pojawia się komunikat o zakończeniu gry. Gdy po obróceniu ekranu fragment gry zostanie utworzony ponownie, przechodzi ze stanu nieaktywnego do aktywnego. Obserwator we fragmencie zostanie ponownie połączony z istniejącym ViewModel i otrzyma aktualne dane. Metoda gameFinished() zostanie ponownie wywołana i wyświetli się komunikat.

W tym zadaniu rozwiążesz ten problem i wyświetlisz komunikat tylko raz, resetując flagę eventGameFinishGameViewModel.

  1. GameViewModel dodaj metodę onGameFinishComplete(), aby zresetować zdarzenie zakończenia gry, _eventGameFinish.
/** Method for the game completed event **/

fun onGameFinishComplete() {
   _eventGameFinish.value = false
}
  1. W pliku GameFragment na końcu pliku gameFinished() wywołaj funkcję onGameFinishComplete() na obiekcie viewModel. (Na razie pozostaw kod nawigacji w gameFinished() jako komentarz).
private fun gameFinished() {
   ...
   viewModel.onGameFinishComplete()
}
  1. Uruchom aplikację i zagraj w grę. Przejdź przez wszystkie słowa, a potem zmień orientację ekranu urządzenia. Komunikat wyświetla się tylko raz.
  2. GameFragment w metodzie gameFinished() odkomentuj kod nawigacji.

    Aby odkomentować kod w Android Studio, wybierz odkomentowane wiersze i naciśnij Control+/ (Command+/ na komputerze Mac).
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score.value?:0
   findNavController(this).navigate(action)
   viewModel.onGameFinishComplete()
}

Jeśli Android Studio wyświetli odpowiedni komunikat, zaimportuj androidx.navigation.fragment.NavHostFragment.findNavController.

  1. Uruchom aplikację i zagraj w grę. Sprawdź, czy po przejściu przez wszystkie słowa aplikacja automatycznie przechodzi do ekranu z wynikiem końcowym.

Doskonale! Aplikacja używa LiveData, aby wywołać zdarzenie zakończenia gry i przekazać z GameViewModel do fragmentu gry informację, że lista słów jest pusta. Fragment gry przechodzi do fragmentu wyniku.

W tym zadaniu zmienisz wynik na obiekt LiveData w pliku ScoreViewModel i dołączysz do niego obserwatora. To zadanie jest podobne do tego, które wykonano podczas dodawania LiveData do GameViewModel.

Wprowadzasz te zmiany w ScoreViewModel, aby zapewnić kompletność danych i aby wszystkie dane w aplikacji korzystały z LiveData.

  1. W polu ScoreViewModel zmień typ zmiennej score na MutableLiveData. Zmień jego nazwę zgodnie z konwencją na _score i dodaj właściwość pomocniczą.
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
   get() = _score
  1. W ScoreViewModel, w bloku init, zainicjuj _score. Możesz usunąć lub pozostawić log w bloku init.
init {
   _score.value = finalScore
}
  1. ScoreFragmentonCreateView() po zainicjowaniu viewModel dołącz obserwatora obiektu wyniku LiveData. W wyrażeniu lambda ustaw wartość wyniku w widoku tekstu wyniku. Usuń z ViewModel kod, który bezpośrednio przypisuje widokowi tekstu wartość wyniku.

Kod do dodania:

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

Kod do usunięcia:

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

Gdy Android Studio wyświetli odpowiedni komunikat, zaimportuj plik androidx.lifecycle.Observer.

  1. Uruchom aplikację i zagraj w grę. Aplikacja powinna działać jak wcześniej, ale teraz do aktualizowania wyniku używa LiveData i obserwatora.

W tym zadaniu dodasz do ekranu z wynikami przycisk Zagraj jeszcze raz i zastosujesz detektor kliknięć za pomocą zdarzenia LiveData. Przycisk wywołuje zdarzenie, które powoduje przejście z ekranu wyniku do ekranu gry.

Kod początkowy aplikacji zawiera przycisk Zagraj ponownie, ale jest on ukryty.

  1. res/layout/score_fragment.xml w przypadku przycisku play_again_button zmień wartość atrybutu visibility na visible.
<Button
   android:id="@+id/play_again_button"
...
   android:visibility="visible"
 />
  1. W sekcji ScoreViewModel dodaj obiekt LiveData, który będzie zawierać element Boolean o nazwie _eventPlayAgain. Ten obiekt służy do zapisywania zdarzenia LiveData, aby przejść z ekranu z wynikami do ekranu gry.
private val _eventPlayAgain = MutableLiveData<Boolean>()
val eventPlayAgain: LiveData<Boolean>
   get() = _eventPlayAgain
  1. ScoreViewModel zdefiniuj metody ustawiania i resetowania zdarzenia _eventPlayAgain.
fun onPlayAgain() {
   _eventPlayAgain.value = true
}
fun onPlayAgainComplete() {
   _eventPlayAgain.value = false
}
  1. W ScoreFragment dodaj obserwatora do eventPlayAgain. Umieść kod na końcu onCreateView(), przed instrukcją return. W wyrażeniu lambda wróć do ekranu gry i zresetuj wartość eventPlayAgain.
// Navigates back to game when button is pressed
viewModel.eventPlayAgain.observe(this, Observer { playAgain ->
   if (playAgain) {
      findNavController().navigate(ScoreFragmentDirections.actionRestart())
       viewModel.onPlayAgainComplete()
   }
})

Po wyświetleniu prośby w Android Studio zaimportuj androidx.navigation.fragment.findNavController.

  1. ScoreFragmentonCreateView() dodaj odbiornik kliknięć do przycisku PlayAgain i wywołaj funkcję viewModel.onPlayAgain().
binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. Uruchom aplikację i zagraj w grę. Po zakończeniu gry na ekranie wyników pojawi się ostateczny wynik i przycisk Zagraj ponownie. Kliknij przycisk PlayAgain, a aplikacja przeniesie Cię na ekran gry, aby umożliwić Ci ponowne rozpoczęcie rozgrywki.

Dobra robota! Zmieniasz architekturę aplikacji, aby używać LiveDataobiektów w ViewModel, i dołączasz obserwatorów do obiektów LiveData. LiveData powiadamia obiekty obserwujące, gdy wartość przechowywana przez LiveData ulegnie zmianie.

Projekt Android Studio: GuessTheWord

LiveData

  • LiveData to klasa przechowująca dane, które można obserwować, i która jest powiązana z cyklem życia. Jest to jeden z komponentów architektury Androida.
  • Możesz użyć LiveData, aby włączyć automatyczne aktualizowanie interfejsu użytkownika po zmianie danych.
  • LiveData jest obserwowalny, co oznacza, że obserwator, np. aktywność lub fragment, może otrzymywać powiadomienia o zmianach danych przechowywanych przez obiekt LiveData.
  • LiveData przechowuje dane; jest to kontener, którego można używać z dowolnymi danymi.
  • LiveData uwzględnia cykl życia, co oznacza, że aktualizuje tylko obserwatorów, którzy są w aktywnym stanie cyklu życia, np. STARTED lub RESUMED.

Aby dodać LiveData

  • Zmień typ zmiennych danych w ViewModel na LiveData lub MutableLiveData.

MutableLiveData to obiekt LiveData, którego wartość można zmienić. MutableLiveData to klasa ogólna, więc musisz określić typ danych, które zawiera.

  • Aby zmienić wartość danych przechowywanych przez zmienną LiveData, użyj metody setValue() w zmiennej LiveData.

Enkapsulacja LiveData

  • Wartość LiveData w obrębie elementu ViewModel powinna być edytowalna. Poza ViewModel element LiveData powinien być czytelny. Można to zaimplementować za pomocą właściwości pomocniczej w języku Kotlin.
  • Właściwość pomocnicza w Kotlinie umożliwia zwracanie z metody pobierającej czegoś innego niż dokładny obiekt.
  • Aby zamknąć element LiveData, użyj private MutableLiveData wewnątrz elementu ViewModel i zwróć właściwość LiveData backing poza elementem ViewModel.

Obserwowalne obiekty LiveData

  • LiveData jest zgodny ze wzorcem obserwatora. „Obserwowany” to obiekt LiveData, a obserwatorami są metody w kontrolerach interfejsu, np. fragmenty. Gdy dane zawarte w LiveData ulegną zmianie, metody obserwatora w kontrolerach interfejsu użytkownika otrzymają powiadomienie.
  • Aby obiekt LiveData był obserwowalny, dołącz do niego obiekt obserwatora w obserwatorach (np. w aktywnościach i fragmentach) za pomocą metody observe().LiveData
  • Ten wzorzec LiveData obserwatora może być używany do komunikacji z kontrolerami interfejsu.ViewModel

Kurs Udacity:

Dokumentacja dla deweloperów aplikacji na Androida:

Inne:

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

Jak zamknąć LiveData w ViewModel, aby obiekty zewnętrzne mogły odczytywać dane bez możliwości ich aktualizacji?

  • W obiekcie ViewModel zmień typ danych na private LiveData. Użyj właściwości pomocniczej, aby udostępnić dane tylko do odczytu typu MutableLiveData.
  • W obiekcie ViewModel zmień typ danych na private MutableLiveData. Użyj właściwości pomocniczej, aby udostępnić dane tylko do odczytu typu LiveData.
  • W kontrolerze interfejsu zmień typ danych na private MutableLiveData. Użyj właściwości pomocniczej, aby udostępnić dane tylko do odczytu typu LiveData.
  • W obiekcie ViewModel zmień typ danych na LiveData. Użyj właściwości pomocniczej, aby udostępnić dane tylko do odczytu typu LiveData.

Pytanie 2

LiveData aktualizuje kontroler interfejsu (np. fragment), jeśli jest on w jednym z tych stanów:

  • Wznowiono
  • W tle
  • Wstrzymano
  • Zatrzymano

Pytanie 3

LiveData wzorcu obserwatora co jest obserwowanym elementem (co jest obserwowane)?

  • Metoda obserwacyjna
  • Dane w obiekcie LiveData
  • Kontroler interfejsu
  • Obiekt ViewModel

Rozpocznij kolejną lekcję: 5.3. Powiązanie danych z ViewModel i LiveData

Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z podstaw języka Kotlin na Androidzie.