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 poprzednich ćwiczeniach z programowania w tej lekcji ulepszyliśmy kod aplikacji GuessTheWord. Aplikacja używa teraz obiektów ViewModel, dzięki czemu dane aplikacji są zachowywane po zmianach konfiguracji urządzenia, takich jak obracanie ekranu czy zmiany dostępności klawiatury. Dodano też obiekt obserwowany LiveData, więc widoki są automatycznie powiadamiane o zmianach w obserwowanych danych.
W tym laboratorium kodowania będziesz kontynuować pracę nad aplikacją GuessTheWord. Powiążesz widoki z klasami ViewModel w aplikacji, aby widoki w układzie komunikowały się bezpośrednio z obiektami ViewModel. (Do tej pory w aplikacji widoki komunikowały się z ViewModel pośrednio za pomocą fragmentów aplikacji). Po zintegrowaniu powiązania danych z obiektami ViewModel nie potrzebujesz już w fragmentach aplikacji obsługi kliknięć, więc możesz ją usunąć.
Zmienisz też aplikację GuessTheWord, aby używała LiveData jako źródła powiązania danych do powiadamiania interfejsu o zmianach w danych bez używania metod obserwatora LiveData.
Co warto wiedzieć
- Jak tworzyć podstawowe aplikacje na Androida w języku Kotlin.
- Jak działają cykle życia aktywności i fragmentów.
- Jak używać obiektów
ViewModelw aplikacji. - Jak przechowywać dane za pomocą
LiveDatawViewModel. - Jak dodać metody obserwatora, aby obserwować zmiany w danych
LiveData.
Czego się nauczysz
- Jak używać elementów biblioteki powiązań danych.
- Jak zintegrować
ViewModelz powiązaniem danych. - Jak zintegrować
LiveDataz powiązaniem danych. - Jak używać powiązań odbiorników, aby zastąpić odbiorniki kliknięć we fragmencie.
- Jak dodawać formatowanie ciągów znaków do wyrażeń wiązania danych.
Jakie zadania wykonasz
- Widoki w układach GuessTheWord komunikują się pośrednio z obiektami
ViewModel, używając kontrolerów interfejsu (fragmentów) do przekazywania informacji. W tych ćwiczeniach powiążesz widoki aplikacji z obiektamiViewModel, aby widoki komunikowały się bezpośrednio z obiektamiViewModel. - Zmieniasz aplikację, aby używała
LiveDatajako źródła powiązań danych. Po tej zmianie obiektyLiveDatapowiadamiają interfejs użytkownika o zmianach w danych, a metody obserwatoraLiveDatanie są już potrzebne.
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, integrując powiązanie danych z LiveData w obiektach ViewModel. Automatyzuje to komunikację między widokami w układzie a obiektami ViewModel i umożliwia uproszczenie kodu przez użycie LiveData.
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ą.
- (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.
- Uruchom aplikację i zagraj w grę.
- Zauważ, że przycisk OK wyświetla następne słowo i zwiększa wynik o 1, a przycisk Pomiń wyświetla następne słowo i zmniejsza wynik o 1. Przycisk Zakończ grę kończy grę.
- Przejdź przez wszystkie słowa i zauważ, że aplikacja automatycznie przechodzi do ekranu z wynikami.
W poprzednim module wykorzystaliśmy powiązanie danych jako bezpieczny pod względem typów sposób uzyskiwania dostępu do widoków w aplikacji GuessTheWord. Prawdziwa moc powiązania danych polega jednak na tym, co sugeruje nazwa: powiązaniu danych bezpośrednio z obiektami widoku w aplikacji.
Obecna architektura aplikacji
W aplikacji widoki są zdefiniowane w układzie XML, a dane tych widoków są przechowywane w obiektach ViewModel. Pomiędzy każdym widokiem a odpowiadającym mu elementem ViewModel znajduje się kontroler interfejsu, który działa jako przekaźnik między nimi.

Na przykład:
- Przycisk OK jest zdefiniowany jako widok
Buttonw pliku układugame_fragment.xml. - Gdy użytkownik kliknie przycisk OK, odbiornik kliknięć we fragmencie
GameFragmentwywoła odpowiedni odbiornik kliknięć we fragmencieGameViewModel. - Wynik zostanie zaktualizowany w
GameViewModel.
Widok Button i GameViewModel nie komunikują się bezpośrednio – potrzebują odbiornika kliknięć w GameFragment.
ViewModel przekazywany do powiązania danych
Byłoby prościej, gdyby widoki w układzie komunikowały się bezpośrednio z danymi w obiektach ViewModel bez polegania na kontrolerach interfejsu jako pośrednikach.

Obiekty ViewModel zawierają wszystkie dane interfejsu w aplikacji GuessTheWord. Przekazując obiekty ViewModel do powiązania danych, możesz zautomatyzować część komunikacji między widokami a obiektami ViewModel.
W tym ćwiczeniu powiążesz klasy GameViewModel i ScoreViewModel z odpowiednimi układami XML. Konfigurujesz też powiązania odbiorników, aby obsługiwać zdarzenia kliknięcia.
Krok 1. Dodaj wiązanie danych dla klasy GameViewModel
W tym kroku powiążesz plik GameViewModel z odpowiednim plikiem układu game_fragment.xml.
- W pliku
game_fragment.xmldodaj zmienną wiązania danych typuGameViewModel. Jeśli w Android Studio występują błędy, wyczyść i ponownie skompiluj projekt.
<layout ...>
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
<androidx.constraintlayout...
- W pliku
GameFragmentprzekażGameViewModeldo powiązania danych.
Aby to zrobić, przypiszviewModeldo zmiennejbinding.gameViewModelzadeklarowanej w poprzednim kroku. Umieść ten kod w taguonCreateView()po zainicjowaniu taguviewModel. Jeśli w Android Studio występują błędy, wyczyść i ponownie skompiluj projekt.
// Set the viewmodel for databinding - this allows the bound layout access
// to all the data in the ViewModel
binding.gameViewModel = viewModelKrok 2. Używaj powiązań odbiorników do obsługi zdarzeń
Powiązania detektorów to wyrażenia powiązań, które są uruchamiane, gdy wyzwalane są zdarzenia takie jak onClick(), onZoomIn() lub onZoomOut(). Powiązania odbiorników są zapisywane jako wyrażenia lambda.
Powiązanie danych tworzy odbiornik i ustawia go w widoku. Gdy nastąpi zdarzenie, którego nasłuchuje detektor, ocenia on wyrażenie lambda. Powiązania odbiorników 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 wiążące.
W tym kroku zastąpisz odbiorniki kliknięć w pliku GameFragment powiązaniami odbiorników w pliku game_fragment.xml.
- W sekcji
game_fragment.xmldodaj atrybutonClickdo elementuskip_button. Zdefiniuj wyrażenie wiążące i wywołaj metodęonSkip()wGameViewModel. To wyrażenie wiążące jest nazywane wiązaniem odbiornika.
<Button
android:id="@+id/skip_button"
...
android:onClick="@{() -> gameViewModel.onSkip()}"
... />- Podobnie powiąż zdarzenie kliknięcia elementu
correct_buttonz metodąonCorrect()w elemencieGameViewModel.
<Button
android:id="@+id/correct_button"
...
android:onClick="@{() -> gameViewModel.onCorrect()}"
... />- Powiąż zdarzenie kliknięcia elementu
end_game_buttonz metodąonGameFinish()wGameViewModel.
<Button
android:id="@+id/end_game_button"
...
android:onClick="@{() -> gameViewModel.onGameFinish()}"
... />- W
GameFragmentusuń instrukcje, które ustawiają odbiorniki kliknięć, oraz funkcje, które te odbiorniki wywołują. Nie potrzebujesz już tych informacji.
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 powiązanie danych dla klasy ScoreViewModel
W tym kroku powiążesz plik ScoreViewModel z odpowiednim plikiem układu score_fragment.xml.
- W pliku
score_fragment.xmldodaj zmienną wiązania typuScoreViewModel. Ten krok jest podobny do tego, który wykonaliśmy w przypadkuGameViewModelpowyżej.
<layout ...>
<data>
<variable
name="scoreViewModel"
type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout- W sekcji
score_fragment.xmldodaj atrybutonClickdo elementuplay_again_button. Zdefiniuj powiązanie odbiornika i wywołaj metodęonPlayAgain()wScoreViewModel.
<Button
android:id="@+id/play_again_button"
...
android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
... />- W
ScoreFragmentwonCreateView()zainicjujviewModel. Następnie zainicjuj zmienną wiązaniabinding.scoreViewModel.
viewModel = ...
binding.scoreViewModel = viewModel- W
ScoreFragmentusuń kod, który ustawia odbiornik kliknięć dla elementuplayAgainButton. Jeśli Android Studio wyświetli błąd, wyczyść i ponownie skompiluj projekt.
Kod do usunięcia:
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }- Uruchom aplikację. Powinna działać jak wcześniej, ale teraz widoki przycisków komunikują się bezpośrednio z obiektami
ViewModel. Widoki nie komunikują się już za pomocą funkcji obsługi kliknięć przycisków wScoreFragment.
Rozwiązywanie problemów z komunikatami o błędach powiązań danych
Gdy aplikacja korzysta z powiązania danych, proces kompilacji generuje klasy pośrednie, które są używane do powiązania danych. Aplikacja może zawierać błędy, których Android Studio nie wykryje, dopóki nie spróbujesz skompilować aplikacji. Dlatego podczas pisania kodu nie widzisz ostrzeżeń ani czerwonego kodu. Podczas kompilacji pojawiają się jednak niejasne błędy pochodzące z wygenerowanych klas pośrednich.
Jeśli pojawi się niejasny komunikat o błędzie:
- Przyjrzyj się uważnie komunikatowi w panelu Build (Kompilacja) w Android Studio. Jeśli widzisz lokalizację, która kończy się na
databinding, oznacza to błąd w powiązaniu danych. - W pliku XML układu sprawdź, czy nie ma błędów w atrybutach
onClick, które używają powiązania danych. Wyszukaj funkcję, do której odwołuje się wyrażenie lambda, i upewnij się, że istnieje. - W sekcji
<data>pliku XML sprawdź pisownię zmiennej powiązania danych.
Na przykład zwróć uwagę na błąd w nazwie funkcji onCorrect() w poniższej wartości atrybutu:
android:onClick="@{() -> gameViewModel.onCorrectx()}"
Zwróć też uwagę na błąd w pisowni słowa 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. Kompilator wyświetla wtedy komunikat o błędzie, np. ten:
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 współpracuje z LiveData używanym z obiektami ViewModel. Po dodaniu powiązania danych do obiektów ViewModel możesz włączyć LiveData.
W tym ćwiczeniu zmienisz aplikację GuessTheWord, aby używała LiveData jako źródła powiązań danych do powiadamiania interfejsu o zmianach w danych bez używania metod obserwatora LiveData.
Krok 1. Dodaj LiveData słowa do pliku game_fragment.xml
W tym kroku powiążesz bieżący widok tekstu słowa bezpośrednio z obiektem LiveData w ViewModel.
- W
game_fragment.xmldodaj atrybutandroid:textdo widoku tekstuword_text.
Ustaw go na obiekt LiveData, word z GameViewModel, używając zmiennej wiążącej gameViewModel.
<TextView
android:id="@+id/word_text"
...
android:text="@{gameViewModel.word}"
... />Pamiętaj, że nie musisz używać znaku word.value. Zamiast tego możesz użyć rzeczywistego obiektu LiveData. Obiekt LiveData wyświetla aktualną wartość word. Jeśli wartość word to null, obiekt LiveData wyświetla pusty ciąg znaków.
- W
GameFragmentwonCreateView()po zainicjowaniugameViewModelustaw bieżącą aktywność jako właściciela cyklu życia zmiennejbinding. Określa to zakres powyższego obiektuLiveData, co umożliwia mu 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 sekcji
GameFragmentusuń obserwatora z kontaLiveDataword.
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ę. Obecne słowo jest teraz aktualizowane bez metody obserwatora w kontrolerze interfejsu.
Krok 2. Dodaj obiekt LiveData wyniku do pliku score_fragment.xml
W tym kroku powiążesz LiveData score z widokiem tekstu wyniku we fragmencie wyniku.
- W
score_fragment.xmldodaj atrybutandroid:textdo widoku tekstu wyniku. PrzypiszscoreViewModel.scoredo atrybututext. Ponieważscorejest liczbą całkowitą, przekształć ją w ciąg znaków za pomocą funkcjiString.valueOf().
<TextView
android:id="@+id/score_text"
...
android:text="@{String.valueOf(scoreViewModel.score)}"
... />- W
ScoreFragmentpo zainicjowaniuscoreViewModelustaw 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
ScoreFragmentusuń 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 partytury jest wyświetlany prawidłowo, bez obserwatora w tym fragmencie.
Krok 3. Dodaj formatowanie ciągów znaków za pomocą wiązania danych
W układzie możesz dodać formatowanie ciągów znaków wraz z wiązaniem danych. W tym zadaniu sformatujesz bieżące słowo, dodając do niego cudzysłów. Musisz też sformatować ciąg znaków z wynikiem, aby dodać do niego prefiks Current Score, jak pokazano na poniższym obrazie.

- W
string.xmldodaj te ciągi tekstowe, których użyjesz do sformatowania widoków tekstuwordiscore. Symbole%si%dto symbole zastępcze dla bieżącego słowa i bieżącego wyniku.
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>- W pliku
game_fragment.xmlzaktualizuj atrybuttextwidoku tekstuword_text, aby używać zasobu ciągu znakówquote_format. Przekaż zagameViewModel.word. Spowoduje to przekazanie bieżącego słowa jako argumentu do ciągu formatującego.
<TextView
android:id="@+id/word_text"
...
android:text="@{@string/quote_format(gameViewModel.word)}"
... />- Sformatuj widok tekstu
scorepodobnie jakword_text. Wgame_fragment.xmldodaj atrybuttextdo widoku tekstuscore_text. Użyj zasobu ciągu znakówscore_format, który przyjmuje jeden argument liczbowy reprezentowany przez obiekt zastępczy%d. Przekaż obiektLiveData,score, jako argument do tego ciągu formatującego.
<TextView
android:id="@+id/score_text"
...
android:text="@{@string/score_format(gameViewModel.score)}"
... />- W klasie
GameFragment, w metodzieonCreateView(), usuń kod obserwatorascore.
Kod do usunięcia:
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})- Wyczyść, ponownie skompiluj i uruchom aplikację, a potem zagraj w grę. Zwróć uwagę, że bieżące słowo i wynik są sformatowane na ekranie gry.

Gratulacje! W aplikacji masz zintegrowane LiveData i ViewModel z powiązaniem danych. Dzięki temu widoki w układzie mogą bezpośrednio komunikować się z ViewModel bez używania w fragmencie funkcji obsługi kliknięć. Używasz też obiektów LiveData jako źródła powiązania danych, aby automatycznie powiadamiać interfejs o zmianach w danych bez użycia metod obserwatora LiveData.
Projekt Android Studio: GuessTheWord
- Biblioteka powiązań danych bezproblemowo współpracuje z komponentami architektury Androida, takimi jak
ViewModeliLiveData. - Układy w aplikacji mogą być powiązane z danymi w komponentach architektury, które już pomagają zarządzać cyklem życia kontrolera interfejsu i powiadamiać o zmianach w danych.
Wiązanie danych ViewModel
- Możesz powiązać
ViewModelz układem za pomocą powiązania danych. - Obiekty
ViewModelzawierają dane interfejsu. Przekazując obiektyViewModeldo powiązania danych, możesz zautomatyzować część komunikacji między widokami a obiektamiViewModel.
Aby powiązać 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
GameFragmentprzekażGameViewModeldo powiązania danych.
binding.gameViewModel = viewModelPowiązania odbiorników
- Powiązania detektorów to wyrażenia powiązań w układzie, które są uruchamiane, gdy wywoływane są zdarzenia kliknięcia, takie jak
onClick(). - Powiązania odbiorników są zapisywane jako wyrażenia lambda.
- Za pomocą powiązań detektorów zastępujesz detektory kliknięć w kontrolerach interfejsu użytkownika powiązaniami detektorów w pliku układu.
- Powiązanie danych tworzy odbiornik i ustawia go w widoku.
android:onClick="@{() -> gameViewModel.onSkip()}"Dodawanie LiveData do wiązania danych
- Obiekty
LiveDatamogą być używane jako źródło powiązania danych, aby automatycznie powiadamiać interfejs o zmianach w danych. - Widok możesz powiązać bezpośrednio z obiektem
LiveDatawViewModel. Gdy wartośćLiveDatawViewModelsię zmieni, widoki w układzie mogą być automatycznie aktualizowane bez użycia metod obserwatora w kontrolerach interfejsu.
android:text="@{gameViewModel.word}"- Aby powiązanie danych
LiveDatadziałało, ustaw bieżącą aktywność (kontroler interfejsu) jako właściciela cyklu życia zmiennejbindingw kontrolerze interfejsu.
binding.lifecycleOwner = thisFormatowanie ciągów znaków za pomocą wiązania danych
- Za pomocą powiązania danych możesz formatować zasób ciągu znaków za pomocą symboli zastępczych, takich jak
%sw przypadku ciągów znaków i%dw przypadku liczb całkowitych. - Aby zaktualizować atrybut
textwidoku, przekaż obiektLiveDatajako argument do ciągu formatującego.
android:text="@{@string/quote_format(gameViewModel.word)}"Kurs Udacity:
Dokumentacja dla deweloperów aplikacji na Androida:
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
Które z tych stwierdzeń dotyczących powiązań odbiorców nie jest prawdziwe?
- Powiązania detektorów to wyrażenia powiązań, które są uruchamiane, gdy wystąpi zdarzenie.
- Powiązania odbiorników działają we wszystkich wersjach wtyczki Androida do obsługi Gradle.
- Powiązania odbiorników są zapisywane jako wyrażenia lambda.
- Powiązania odbiorników są podobne do odwołań do metod, ale umożliwiają uruchamianie dowolnych wyrażeń powiązania danych.
Pytanie 2
Załóżmy, że Twoja aplikacja zawiera ten zasób w postaci ciągu tekstowego:<string name="generic_name">Hello %s</string>
Która z poniższych opcji zawiera prawidłową składnię formatowania ciągu znaków przy użyciu wyrażenia wiązania danych?
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 wyrażenie powiązane z odbiornikiem jest oceniane i uruchamiane?
- Gdy dane przechowywane przez
LiveDataulegną zmianie - Gdy aktywność zostanie ponownie utworzona w wyniku zmiany konfiguracji
- Gdy wystąpi zdarzenie, np.
onClick() - Gdy aktywność przechodzi w tło
Rozpocznij kolejną lekcję:
Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z podstaw języka Kotlin na Androidzie.


