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.
Wstęp
Podczas poprzednich lekcji udało Ci się ulepszyć kod aplikacji GuessTheWord. Aplikacja korzysta teraz z obiektów ViewModel
, więc dane pozostają w stanie po zmianie konfiguracji urządzenia, np. o obróceniu ekranu i zmianie dostępności klawiatury. Dodano też możliwość obserwowania okresu LiveData
, dzięki czemu powiadomienia są automatycznie powiadamiane o zmianach.
W tym ćwiczeniu z programowania będziesz pracować z aplikacją GuessTheWord. Powiążesz widoki z klasami ViewModel
w aplikacji, aby widoki w Twoim układzie komunikowały się bezpośrednio z obiektami ViewModel
. (Do tej pory wyświetlenia w aplikacji przekazywały pośrednio informacje od ViewModel
z użyciem fragmentów aplikacji). Po zintegrowaniu wiązania danych z obiektami ViewModel
nie potrzebujesz już modułów obsługi kliknięć we fragmentach kodu aplikacji, więc je usuniesz.
Możesz też zmodyfikować aplikację GuesTheWord jako źródło wiążących danych i powiadomić użytkownika o zmianach w danych bez korzystania z metod LiveData
.
Co musisz wiedzieć
- Jak tworzyć podstawowe aplikacje na Androida w Kotlinie.
- Jak działają cykle życia aktywności i fragmentów.
- Jak używać obiektów
ViewModel
w aplikacji. - Jak przechowywać dane za pomocą
LiveData
w narzędziuViewModel
. - Jak dodać metody obserwacji, aby obserwować zmiany w danych
LiveData
.
Czego się nauczysz
- Jak używać elementów z Biblioteki wiązania danych
- Jak zintegrować
ViewModel
z wiązaniem danych. - Jak zintegrować
LiveData
z wiązaniem danych. - Jak używać powiązań słuchaczy, by zastąpić detektory kliknięć we fragmencie.
- Jak dodać formatowanie ciągu do wyrażeń wiążących dane.
Jakie zadania wykonasz:
- Widoki w układach GuessTheWord komunikują się pośrednio z obiektami
ViewModel
, korzystając z kontrolerów (fragmentów) interfejsu użytkownika, aby przekazywać informacje. W ramach tego ćwiczenia powiążesz widoki aplikacji z obiektamiViewModel
, tak by widoki komunikowania się bezpośrednio z obiektamiViewModel
. - Zmieniasz aplikację, aby używać źródła danych jako
LiveData
. Po tej zmianie obiektyLiveData
powiadamiają interfejs użytkownika o zmianach w danych, a metody obserwatoraLiveData
nie są już potrzebne.
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).
Dzięki nim dowiesz się, jak ulepszyć aplikację GuessTheWord, integrując wiązanie danych z obiektem LiveData
w obiektach ViewModel
. Automatyzuje ona komunikację między widokami w układzie i obiektami ViewModel
, a także upraszcza korzystanie z kodu za pomocą metody LiveData
.
Ekran tytułowy |
Ekran gry |
Ekran wyniku |
W tym zadaniu znajdziesz i uruchomisz kod startowy tego ćwiczenia z programowania. Możesz użyć aplikacji GuessTheWord, która została opracowana w ramach poprzedniego ćwiczenia z programowania, lub pobrać aplikację inicjowaną.
- (Opcjonalnie) Jeśli nie używasz kodu z poprzedniego ćwiczenia z programowaniem, pobierz kod startowy tego ćwiczenia. Rozpakuj kod i otwórz projekt w Android Studio.
- Uruchom aplikację i zagraj w grę.
- Przycisk OK wyświetla następne słowo i podnosi wynik o jeden, a przycisk Pomiń pokazuje kolejne słowo i zmniejsza wynik o 1. Przycisk Zakończ grę kończy grę.
- Przełączaj się między słowami i zauważ, że aplikacja automatycznie przechodzi do ekranu z wynikiem.
W poprzednim ćwiczeniu z programowania zdarzyło Ci się wiązać dane w sposób zapewniający dostęp do widoków w aplikacji GuessTheWord. Prawdziwą zaletą wiązania danych jest to, że sugerujemy powiązanie danych bezpośrednio z widokami aplikacji.
Aktualna architektura aplikacji
W aplikacji widoki są zdefiniowane w układzie XML, a dane dla tych widoków są przechowywane w obiektach ViewModel
. Każdy widok danych i odpowiadający mu element ViewModel
to kontroler interfejsu, który pełni rolę przekaźnika.
Przykład:
- Przycisk OK jest zdefiniowany jako widok
Button
w pliku układugame_fragment.xml
. - Gdy użytkownik kliknie przycisk Rozumiem, detektor kliknięć we fragmencie
GameFragment
wywoła odpowiedni moduł nasłuchujący kliknięć w aplikacjiGameViewModel
. - Wynik zostanie zaktualizowany w
GameViewModel
.
Widok danych Button
i GameViewModel
nie komunikują się bezpośrednio – potrzebują detektora kliknięć podanego w GameFragment
.
ViewModel przekazany do powiązania danych
Będzie łatwiej, jeśli widoki w układzie komunikują się bezpośrednio z danymi w obiektach ViewModel
bez konieczności korzystania z kontrolerów interfejsu jako pośredników.
Obiekty ViewModel
zawierają wszystkie dane interfejsu użytkownika dostępne w aplikacji GuessTheWord. Przekazując obiekty ViewModel
do powiązania danych, możesz zautomatyzować komunikację między obiektami a obiektami ViewModel
.
W tym zadaniu kojarzysz klasy GameViewModel
i ScoreViewModel
z odpowiadającymi im układami XML. Konfigurujesz też powiązania słuchacza z danymi o kliknięciach.
Krok 1. Dodaj wiązanie danych do GameViewModel
W tym kroku powiążesz element GameViewModel
z odpowiednim plikiem układu game_fragment.xml
.
- W pliku
game_fragment.xml
dodaj zmienną wiązania danych typuGameViewModel
. Jeśli w Android Studio występują błędy, wyczyść projekt i utwórz go od nowa.
<layout ...>
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
<androidx.constraintlayout...
- W pliku
GameFragment
przekażGameViewModel
do wiązania danych.
W tym celu przypisz elementviewModel
do zmiennejbinding.gameViewModel
zadeklarowanej w poprzednim kroku. Umieść ten kod w sekcjionCreateView()
po inicjowaniu elementuviewModel
. Jeśli w Android Studio występują błędy, wyczyść projekt i utwórz go od nowa.
// Set the viewmodel for databinding - this allows the bound layout access
// to all the data in the ViewModel
binding.gameViewModel = viewModel
Krok 2. Używaj powiązań detektora do obsługi zdarzeń
Wiązania detektorów to wyrażenia wiążące, które są uruchamiane po wywołaniu zdarzeń takich jak onClick()
, onZoomIn()
lub onZoomOut()
. Wiązania detektorów są zapisywane jako wyrażenia lambda.
Wiązanie danych tworzy detektor i ustawia detektor w widoku. Kiedy wystąpi nasłuchiwanie zdarzenia, detektor ocenia wyrażenie lambda. Wiązania słuchaczy działają z wtyczką Androida do obsługi Gradle w wersji 2.0 lub nowszej. Więcej informacji znajdziesz w artykule Układy i wyrażenia powiązania.
W tym kroku zastąpisz detektory kliknięć w pliku GameFragment
powiązaniami detektorów w pliku game_fragment.xml
.
- W
game_fragment.xml
dodaj atrybutonClick
doskip_button
. Zdefiniuj wyrażenie powiązania i wywołaj metodęonSkip()
wGameViewModel
. Jest to tzw. powiązanie słuchacza.
<Button
android:id="@+id/skip_button"
...
android:onClick="@{() -> gameViewModel.onSkip()}"
... />
- Podobnie powiąż zdarzenie kliknięcia elementu
correct_button
z metodąonCorrect
()
w narzędziuGameViewModel
.
<Button
android:id="@+id/correct_button"
...
android:onClick="@{() -> gameViewModel.onCorrect()}"
... />
- Powiąż zdarzenie kliknięcia elementu
end_game_button
z metodąonGameFinish
()
w narzędziuGameViewModel
.
<Button
android:id="@+id/end_game_button"
...
android:onClick="@{() -> gameViewModel.onGameFinish()}"
... />
- W usłudze
GameFragment
usuń instrukcje, które ustawiają detektory kliknięć, i usuń funkcje wywoływane przez detektory kliknięć. Nie są Ci już potrzebne.
Kod do usunięcia:
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
binding.endGameButton.setOnClickListener { onEndGame() }
/** Methods for buttons presses **/
private fun onSkip() {
viewModel.onSkip()
}
private fun onCorrect() {
viewModel.onCorrect()
}
private fun onEndGame() {
gameFinished()
}
Krok 3. Dodaj wiązanie danych do obiektu ScoreViewModel
W tym kroku powiążesz element ScoreViewModel
z odpowiednim plikiem układu score_fragment.xml
.
- W pliku
score_fragment.xml
dodaj zmienną wiązania typuScoreViewModel
. Ten krok jest podobny do wykonanego przez Ciebie powyżej dla krokuGameViewModel
.
<layout ...>
<data>
<variable
name="scoreViewModel"
type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
- W
score_fragment.xml
dodaj atrybutonClick
doplay_again_button
. Zdefiniuj powiązanie detektora i wywołaj metodęonPlayAgain()
wScoreViewModel
.
<Button
android:id="@+id/play_again_button"
...
android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
... />
- W narzędziu
ScoreFragment
wewnątrzonCreateView()
zainicjujviewModel
. Następnie zainicjuj zmienną powiązaniabinding.scoreViewModel
.
viewModel = ...
binding.scoreViewModel = viewModel
- W kodzie
ScoreFragment
usuń kod, który ustawia detektor kliknięćplayAgainButton
. Jeśli w Android Studio wyświetla się błąd, wyczyść projekt i utwórz go od nowa.
Kod do usunięcia:
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }
- Uruchom swoją aplikację. Aplikacja powinna działać tak jak wcześniej, ale teraz widoki przycisku automatycznie się komunikują z obiektami
ViewModel
. Widoki nie mogą już komunikować się za pomocą modułów obsługi kliknięć przycisku wScoreFragment
.
Rozwiązywanie problemów związanych z komunikatami o błędach wiązania danych
Gdy aplikacja korzysta z powiązania danych, proces kompilacji generuje klasy pośrednie używane do wiązania danych. Aplikacja może zawierać błędy, których Android Studio nie wykryje do czasu, gdy skompilujesz aplikację, więc podczas jej tworzenia nie będziesz widzieć ostrzeżeń ani czerwonego kodu. Jednak w momencie kompilacji pojawiają się błędy tajne pochodzące z wygenerowanych klas pośrednich.
Jeśli zobaczysz zagadkowy komunikat o błędzie:
- Przeczytaj komunikat w panelu kompilacji Android Studio. Jeśli zobaczysz lokalizację, której nazwa kończy się
databinding
, wystąpił błąd powiązania danych. - Sprawdź, czy w pliku XML układu nie występują błędy w atrybutach
onClick
wiążących dane. Znajdź funkcję wywołującą wyrażenie lambda i upewnij się, że istnieje. - W sekcji
<data>
pliku XML sprawdź pisownię zmiennej wiązania danych.
Zwróć uwagę na przykład na błąd w nazwie funkcji onCorrect()
w tej wartości atrybutu:
android:onClick="@{() -> gameViewModel.onCorrectx()}"
Sprawdź też pisownię błędu gameViewModel
w sekcji <data>
pliku XML:
<data>
<variable
name="gameViewModelx"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
Android Studio nie wykrywa takich błędów, dopóki nie skompilujesz aplikacji, a kompilator wyświetli komunikat o błędzie podobny do tego:
error: cannot find symbol import com.example.android.guesstheword.databinding.GameFragmentBindingImpl" symbol: class GameFragmentBindingImpl location: package com.example.android.guesstheword.databinding
Wiązanie danych dobrze działa z elementami LiveData
, które są używane z obiektami ViewModel
. Po dodaniu powiązania danych do obiektów ViewModel
możesz zacząć stosować LiveData
.
W tym zadaniu zmienisz aplikację GuessTheWord tak, aby wykorzystywała LiveData
jako źródło wiązania danych do powiadamiania interfejsu użytkownika o zmianach w danych bez korzystania z metod obserwacji LiveData
.
Krok 1. Dodaj słowo LiveData do pliku game_fragment.xml
W tym kroku powiążesz bieżący widok tekstu słowa bezpośrednio z obiektem LiveData
w elemencie ViewModel
.
- W
game_fragment.xml
dodaj atrybutandroid:text
do widoku tekstuword_text
.
Ustaw jako obiekt LiveData
obiekt word
z GameViewModel
za pomocą zmiennej wiązania gameViewModel
.
<TextView
android:id="@+id/word_text"
...
android:text="@{gameViewModel.word}"
... />
Uwaga: nie musisz używać atrybutu word.value
. Zamiast niego możesz użyć rzeczywistego obiektu LiveData
. Obiekt LiveData
wyświetla bieżącą wartość elementu word
. Jeśli wartość word
ma wartość null, obiekt LiveData
wyświetla pusty ciąg znaków.
- W
GameFragment
w narzędziuonCreateView()
po zainicjowaniugameViewModel
ustaw bieżącą aktywność jako właściciel cyklu życia zmiennejbinding
. Określa zakres obiektuLiveData
powyżej, umożliwiając automatyczne aktualizowanie widoków w układziegame_fragment.xml
.
binding.gameViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
- W
GameFragment
usuń obserwatora dla elementuLiveData
word
.
Kod do usunięcia:
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
binding.wordText.text = newWord
})
- Uruchom aplikację i zagraj w grę. Bieżące słowo jest aktualizowane w interfejsie bez metody obserwatora.
Krok 2. Dodaj wynik LiveData do pliku score_fragment.xml
W tym kroku powiążesz element LiveData
score
z widokiem tekstu wyniku we fragmencie wyniku.
- W
score_fragment.xml
dodaj atrybutandroid:text
do widoku tekstu wyniku. PrzypiszscoreViewModel.score
do atrybututext
. Parametrscore
jest liczbą całkowitą, dlatego należy przekonwertować go na ciąg znaków za pomocą funkcjiString.valueOf()
.
<TextView
android:id="@+id/score_text"
...
android:text="@{String.valueOf(scoreViewModel.score)}"
... />
- W
ScoreFragment
po zainicjowaniuscoreViewModel
ustaw bieżącą aktywność jako właściciela cyklu życia zmiennejbinding
.
binding.scoreViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
- W
ScoreFragment
usuń obserwatora obiektuscore
.
Kod do usunięcia:
// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
- Uruchom aplikację i zagraj w grę. Zwróć uwagę, że wynik w fragmencie wyniku jest wyświetlany prawidłowo, bez obserwatora we fragmencie wyniku.
Krok 3. Dodaj formatowanie ciągu znaków przy użyciu wiązania danych
W układzie możesz dodać formatowanie ciągu razem z wiązaniem danych. W tym zadaniu formatujesz obecne słowo, aby dodać w nim cudzysłów. Formatujesz też wynik, dodając do niego bieżący wynik, jak pokazano na poniższym obrazie.
- W
string.xml
dodaj te ciągi znaków, które będą służyć do formatowania widoków tekstowychword
iscore
.%s
i%d
są obiektami zastępczymi bieżącego słowa i aktualnego wyniku.
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>
- W narzędziu
game_fragment.xml
zaktualizuj atrybuttext
widoku tekstowegoword_text
, aby używał zasobu ciągu znakówquote_format
. Przejdź zagameViewModel.word
. Przekazuje ono aktualne słowo jako argument do ciągu formatowania.
<TextView
android:id="@+id/word_text"
...
android:text="@{@string/quote_format(gameViewModel.word)}"
... />
- Formatuj widok tekstowy
score
podobny doword_text
. Wgame_fragment.xml
dodaj atrybuttext
do widoku tekstuscore_text
. Użyj zasobu ciągu znakówscore_format
, który przyjmuje 1 argument liczbowy reprezentowany przez symbol zastępczy%d
. Jako argument tego ciągu formatowania przekaż obiektLiveData
(score
).
<TextView
android:id="@+id/score_text"
...
android:text="@{@string/score_format(gameViewModel.score)}"
... />
- W klasie
GameFragment
usuń kod obserwatora z metodyscore
.
Kod do usunięcia:
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
- Wyczyść, odtwórz i uruchom aplikację, a potem zagraj w grę. Zwróć uwagę, że bieżące słowo i wynik zostaną sformatowane na ekranie gry.
Gratulacje! Zintegrowano LiveData
i ViewModel
z wiązaniem danych w aplikacji. Umożliwi to bezpośrednie komunikowanie się z elementami ViewModel
w układzie z wykorzystaniem modułów obsługi kliknięć we fragmencie. Obiekty LiveData
zostały też wykorzystane jako źródło wiązania danych do automatycznego powiadamiania interfejsu użytkownika o zmianach w danych, bez korzystania z metod obserwacji LiveData
.
Projekt na Android Studio: GuessTheWord
- Biblioteka wiązania danych działa bezproblemowo z komponentami Android Architecture, takimi jak
ViewModel
iLiveData
. - Układy w aplikacji mogą tworzyć powiązanie z danymi w komponentach architektury, które ułatwiają zarządzanie cyklem życia interfejsu użytkownika i powiadamianie o zmianach w danych.
Wiązanie danych ViewModel
- Możesz powiązać obiekt
ViewModel
z układem, używając wiązania danych. - Obiekty
ViewModel
zawierają dane interfejsu użytkownika. Przekażąc obiektyViewModel
do powiązania danych, możesz zautomatyzować komunikację między widokami a obiektamiViewModel
.
Jak powiązać element ViewModel
z układem:
- W pliku układu dodaj zmienną wiązania danych typu
ViewModel
.
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
- W pliku
GameFragment
przekażGameViewModel
do wiązania danych.
binding.gameViewModel = viewModel
Powiązania detektora
- Wiązania detektorów to wyrażenia w układzie, które są uruchamiane po wywołaniu zdarzeń kliknięcia, takich jak
onClick()
. - Wiązania detektorów są zapisywane jako wyrażenia lambda.
- Za pomocą powiązań słuchaczy możesz zastąpić detektory kliknięć w kontrolerach interfejsu wiązaniami detektora w pliku układu.
- Wiązanie danych tworzy detektor i ustawia detektor w widoku.
android:onClick="@{() -> gameViewModel.onSkip()}"
Dodawanie LiveData do wiązania danych
- Obiekty
LiveData
mogą służyć jako źródło wiązania danych, aby automatycznie powiadamiać interfejs użytkownika o zmianach w danych. - Możesz powiązać ten widok bezpośrednio z obiektem
LiveData
w narzędziuViewModel
. GdyLiveData
w narzędziuViewModel
ulegnie zmianie, widoki w układzie mogą być aktualizowane automatycznie bez użycia metod obserwacji w kontrolerach interfejsu.
android:text="@{gameViewModel.word}"
- Aby powiązanie danych
LiveData
działało, ustaw obecną aktywność (kontroler interfejsu) jako właściciela cyklu życia zmiennejbinding
w kontrolerze interfejsu.
binding.lifecycleOwner = this
Formatowanie ciągów z wiązaniem danych
- Dzięki wiązaniu danych możesz sformatować zasób ciągu tekstowego za pomocą symboli zastępczych, takich jak
%s
w przypadku ciągów tekstowych i%d
w przypadku liczb całkowitych. - Aby zaktualizować atrybut
text
widoku, przekaż obiektLiveData
jako argument ciągu tekstowego.
android:text="@{@string/quote_format(gameViewModel.word)}"
Kurs Udacity:
Dokumentacja dla programistów Androida:
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
Które z poniższych stwierdzeń na temat powiązań słuchaczy nie jest prawdziwe?
- Powiązania detektora to wyrażenia powiązania, które są uruchamiane, gdy wystąpi zdarzenie.
- Powiązania detektora działają ze wszystkimi wersjami wtyczki Androida do obsługi Gradle.
- Wiązania detektorów są zapisywane jako wyrażenia lambda.
- Wiązania detektorów są podobne do odniesień do metod, ale pozwalają uruchamiać dowolne wyrażenia wiążące dane.
Pytanie 2
Załóżmy, że Twoja aplikacja zawiera ten zasób ciągu znaków:<string name="generic_name">Hello %s</string>
Która z tych odpowiedzi jest prawidłowa, aby sformatować ciąg znaków przy użyciu wyrażenia wiążącego dane?
android:text= "@{@string/generic_name(user.name)}"
android:text= "@{string/generic_name(user.name)}"
android:text= "@{@generic_name(user.name)}"
android:text= "@{@string/generic_name,user.name}"
Pytanie 3
Kiedy następuje ocenianie i powiązanie detektora?
- Po zmianie danych przechowywanych w
LiveData
- Gdy aktywność zostanie ponownie utworzona przez zmianę konfiguracji
- Kiedy wystąpi zdarzenie takie jak
onClick()
- Gdy aktywność zaczyna działać w tle
Rozpocznij następną lekcję:
Linki do innych ćwiczeń z programowania w tym kursie znajdziesz na stronie docelowej z ćwiczeniami z podstaw Androida Kotlin.