Эта практическая работа входит в курс «Основы Android Kotlin». Вы получите максимальную пользу от этого курса, если будете выполнять практические работы последовательно. Все практические работы курса перечислены на целевой странице практической работы «Основы Android Kotlin» .
Введение
В предыдущих практических работах этого урока вы улучшили код приложения GuessTheWord. Теперь приложение использует объекты ViewModel , поэтому данные приложения сохраняются при изменении конфигурации устройства, например, при повороте экрана и изменении доступности клавиатуры. Вы также добавили наблюдаемые LiveData , поэтому представления автоматически уведомляются об изменении наблюдаемых данных.
В этой лабораторной работе вы продолжите работу с приложением GuessTheWord. Вы привяжете представления к классам ViewModel в приложении, чтобы представления в вашем макете напрямую взаимодействовали с объектами ViewModel . (До сих пор представления в вашем приложении взаимодействовали с ViewModel косвенно , через фрагменты приложения.) После интеграции привязки данных к объектам ViewModel обработчики щелчков во фрагментах приложения больше не нужны, поэтому вы их удаляете.
Вы также изменяете приложение GuessTheWord так, чтобы оно использовало LiveData в качестве источника привязки данных для уведомления пользовательского интерфейса об изменениях в данных без использования методов наблюдателя LiveData .
Что вам уже следует знать
- Как создавать простые приложения для Android на Kotlin.
- Как работают жизненные циклы активностей и фрагментов.
- Как использовать объекты
ViewModelв вашем приложении. - Как хранить данные с помощью
LiveDataвViewModel. - Как добавить методы наблюдателя для наблюдения за изменениями в данных
LiveData.
Чему вы научитесь
- Как использовать элементы библиотеки привязки данных .
- Как интегрировать
ViewModelс привязкой данных. - Как интегрировать
LiveDataс привязкой данных. - Как использовать привязки прослушивателей для замены прослушивателей щелчков во фрагменте.
- Как добавить форматирование строк в выражения привязки данных.
Что ты будешь делать?
- Представления в макетах GuessTheWord взаимодействуют с объектами
ViewModelкосвенно, используя контроллеры пользовательского интерфейса (фрагменты) для передачи информации. В этой лабораторной работе вы свяжете представления приложения с объектамиViewModel, чтобы эти представления взаимодействовали напрямую с объектамиViewModel. - Вы изменяете приложение так, чтобы оно использовало
LiveDataв качестве источника привязки данных. После этого изменения объектыLiveDataуведомляют пользовательский интерфейс об изменениях данных, и методы наблюдателяLiveDataбольше не нужны.
В практических занятиях по коду Урока 5 вы разработаете приложение GuessTheWord, начав с базового кода. GuessTheWord — это игра в стиле шарады для двух игроков, где игроки объединяют усилия, чтобы набрать как можно больше очков.
Первый игрок смотрит на слова в приложении и по очереди разыгрывает каждое слово, не показывая его второму игроку. Второй игрок пытается угадать слово.
Чтобы начать игру, первый игрок открывает приложение на устройстве и видит слово, например, «гитара», как показано на снимке экрана ниже.
Первый игрок изображает слово, стараясь не произносить его целиком.
- Когда второй игрок правильно отгадывает слово, первый игрок нажимает кнопку « Понял» , что увеличивает счет на единицу и показывает следующее слово.
- Если второй игрок не может угадать слово, первый игрок нажимает кнопку «Пропустить» , что уменьшает счет на единицу и переходит к следующему слову.
- Чтобы завершить игру, нажмите кнопку «Завершить игру» . (Эта функция отсутствует в стартовом коде первой лабораторной работы в серии.)
В этой лабораторной работе вы улучшите приложение GuessTheWord, интегрировав привязку данных к LiveData в объектах ViewModel . Это автоматизирует взаимодействие между представлениями в макете и объектами ViewModel и позволяет упростить код за счёт использования LiveData .
Титульный экран |
Игровой экран |
Экран результатов |
В этом задании вам предстоит найти и запустить стартовый код для этой лабораторной работы. В качестве стартового кода вы можете использовать приложение GuessTheWord, созданное в предыдущей лабораторной работе, или скачать готовое стартовое приложение.
- (Необязательно) Если вы не используете код из предыдущей лабораторной работы, скачайте начальный код для этой. Распакуйте код и откройте проект в Android Studio.
- Запустите приложение и играйте в игру.
- Обратите внимание, что кнопка «Понял» показывает следующее слово и увеличивает счёт на один, а кнопка «Пропустить» показывает следующее слово и уменьшает счёт на один. Кнопка «Завершить игру» завершает игру.
- Просмотрите все слова и обратите внимание, что приложение автоматически переходит на экран с очками.
В предыдущей лабораторной работе вы использовали привязку данных как типобезопасный способ доступа к представлениям в приложении GuessTheWord. Но настоящая сила привязки данных заключается в том, что она делает то, что следует из названия: привязывает данные непосредственно к объектам представлений в вашем приложении.
Текущая архитектура приложения
В вашем приложении представления определяются в XML-макете, а данные для них хранятся в объектах ViewModel . Между каждым представлением и соответствующей ему ViewModel находится контроллер пользовательского интерфейса, который выступает в роли связующего звена между ними.

Например:
- Кнопка «Понял» определена как представление
Buttonв файле макетаgame_fragment.xml. - Когда пользователь нажимает кнопку «Понял» , прослушиватель щелчков во фрагменте
GameFragmentвызывает соответствующий прослушиватель щелчков вGameViewModel. - Счет обновляется в
GameViewModel.
Представление Button и GameViewModel не взаимодействуют напрямую — им нужен прослушиватель щелчков, который находится в GameFragment .
ViewModel передается в привязку данных
Было бы проще, если бы представления в макете напрямую взаимодействовали с данными в объектах ViewModel , не полагаясь на контроллеры пользовательского интерфейса в качестве посредников.

Объекты ViewModel содержат все данные пользовательского интерфейса в приложении GuessTheWord. Передавая объекты ViewModel в привязку данных, можно автоматизировать часть взаимодействия между представлениями и объектами ViewModel .
В этой задаче вы свяжете классы GameViewModel и ScoreViewModel с соответствующими им XML-макетами. Вы также настроите привязки прослушивателей для обработки событий щелчков.
Шаг 1: Добавьте привязку данных для GameViewModel
На этом этапе вы связываете GameViewModel с соответствующим файлом макета game_fragment.xml .
- В файле
game_fragment.xmlдобавьте переменную привязки данных типаGameViewModel. Если в Android Studio возникли ошибки, исправьте их и пересоберите проект.
<layout ...>
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
<androidx.constraintlayout...
- В файле
GameFragmentпередайтеGameViewModelв привязку данных.
Для этого присвойтеviewModelпеременнойbinding.gameViewModel, объявленной на предыдущем шаге. Вставьте этот код вonCreateView()после инициализацииviewModel. Если в Android Studio возникли ошибки, исправьте их и пересоберите проект.
// Set the viewmodel for databinding - this allows the bound layout access
// to all the data in the ViewModel
binding.gameViewModel = viewModelШаг 2: Используйте привязки прослушивателя для обработки событий
Привязки прослушивателя — это выражения привязки, которые выполняются при возникновении таких событий, как onClick() , onZoomIn() или onZoomOut() . Привязки прослушивателя записываются в виде лямбда-выражений.
Привязка данных создаёт прослушиватель и устанавливает его в представлении. При возникновении ожидаемого события прослушиватель вычисляет лямбда-выражение. Привязки прослушивателей работают с плагином Android Gradle версии 2.0 и выше. Подробнее см. в разделе Макеты и выражения привязки .
На этом этапе вы заменяете прослушиватели щелчков в GameFragment привязками прослушивателей в файле game_fragment.xml .
- В
game_fragment.xmlдобавьте атрибутonClickкskip_button. Определите выражение привязки и вызовите методonSkip()вGameViewModel. Это выражение привязки называется привязкой слушателя .
<Button
android:id="@+id/skip_button"
...
android:onClick="@{() -> gameViewModel.onSkip()}"
... />- Аналогично привяжите событие нажатия
correct_buttonк методуonCorrect()вGameViewModel.
<Button
android:id="@+id/correct_button"
...
android:onClick="@{() -> gameViewModel.onCorrect()}"
... />- Свяжите событие нажатия кнопки
end_game_buttonс методомonGameFinish()вGameViewModel.
<Button
android:id="@+id/end_game_button"
...
android:onClick="@{() -> gameViewModel.onGameFinish()}"
... />- В
GameFragmentудалите операторы, устанавливающие обработчики щелчков, и удалите функции, которые они вызывают. Они вам больше не понадобятся.
Код для удаления:
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()
}Шаг 3: Добавьте привязку данных для ScoreViewModel
На этом этапе вы связываете ScoreViewModel с соответствующим файлом макета, score_fragment.xml .
- В файле
score_fragment.xmlдобавьте переменную привязки типаScoreViewModel. Этот шаг аналогичен тому, что вы сделали дляGameViewModelвыше.
<layout ...>
<data>
<variable
name="scoreViewModel"
type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout- В
score_fragment.xmlдобавьте атрибутonClickкplay_again_button. Определите привязку прослушивателя и вызовите методonPlayAgain()вScoreViewModel.
<Button
android:id="@+id/play_again_button"
...
android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
... />- В
ScoreFragment, внутриonCreateView(), инициализируйтеviewModel. Затем инициализируйте переменную привязкиbinding.scoreViewModel.
viewModel = ...
binding.scoreViewModel = viewModel- В
ScoreFragmentудалите код, который устанавливает прослушиватель кликов дляplayAgainButton. Если Android Studio выдаёт ошибку, очистите и пересоберите проект.
Код для удаления:
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }- Запустите приложение. Оно должно работать как и раньше, но теперь представления кнопок взаимодействуют напрямую с объектами
ViewModel. Представления больше не взаимодействуют через обработчики нажатий кнопок вScoreFragment.
Устранение неполадок с сообщениями об ошибках привязки данных
Когда приложение использует привязку данных, процесс компиляции генерирует промежуточные классы, которые используются для этой привязки. Приложение может содержать ошибки, которые Android Studio не обнаруживает, пока вы не попытаетесь скомпилировать приложение, поэтому вы не видите предупреждений или красного кода во время написания кода. Однако во время компиляции вы получаете непонятные ошибки, связанные с сгенерированными промежуточными классами.
Если вы получили непонятное сообщение об ошибке:
- Внимательно посмотрите на сообщение на панели сборки Android Studio. Если вы видите расположение, заканчивающееся на
databinding, это означает, что произошла ошибка привязки данных. - В XML-файле макета проверьте наличие ошибок в атрибутах
onClick, использующих привязку данных. Найдите функцию, вызываемую лямбда-выражением, и убедитесь, что она существует. - В разделе
<data>XML проверьте правильность написания переменной привязки данных.
Например, обратите внимание на неправильное написание имени функции onCorrect() в следующем значении атрибута:
android:onClick="@{() -> gameViewModel.onCorrectx()}"
Также обратите внимание на опечатку в написании gameViewModel в разделе <data> XML-файла:
<data>
<variable
name="gameViewModelx"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>Android Studio не обнаруживает подобные ошибки, пока приложение не скомпилируется, после чего компилятор выводит сообщение об ошибке, подобное следующему:
error: cannot find symbol import com.example.android.guesstheword.databinding.GameFragmentBindingImpl" symbol: class GameFragmentBindingImpl location: package com.example.android.guesstheword.databinding
Привязка данных хорошо работает с LiveData , используемой с объектами ViewModel . Теперь, когда вы добавили привязку данных к объектам ViewModel , вы готовы к интеграции LiveData .
В этой задаче вы измените приложение GuessTheWord так, чтобы оно использовало LiveData в качестве источника привязки данных для уведомления пользовательского интерфейса об изменениях в данных, не прибегая к методам наблюдателя LiveData .
Шаг 1: Добавьте слово LiveData в файл game_fragment.xml.
На этом этапе вы привязываете текущее текстовое представление слова непосредственно к объекту LiveData в ViewModel .
- В
game_fragment.xmlдобавьте атрибутandroid:textк текстовому представлениюword_text.
Установите его для объекта LiveData , word из GameViewModel , используя переменную привязки gameViewModel .
<TextView
android:id="@+id/word_text"
...
android:text="@{gameViewModel.word}"
... />Обратите внимание, что вам не обязательно использовать word.value . Вместо этого вы можете использовать сам объект LiveData . Объект LiveData отображает текущее значение word . Если значение word равно null, объект LiveData отображает пустую строку.
- В
GameFragment, вonCreateView(), после инициализацииgameViewModel, установите текущую активность в качестве владельца жизненного цикла переменнойbinding. Это определяет область действия объектаLiveData, указанную выше, позволяя объекту автоматически обновлять представления в макетеgame_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- В
GameFragmentудалите наблюдателя дляwordLiveData.
Код для удаления:
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
binding.wordText.text = newWord
})- Запустите приложение и играйте. Теперь текущее слово обновляется без метода-наблюдателя в контроллере пользовательского интерфейса.
Шаг 2: Добавьте данные LiveData оценки в файл score_fragment.xml
На этом этапе вы привязываете score LiveData к текстовому представлению оценки во фрагменте оценки.
- В
score_fragment.xmlдобавьте атрибутandroid:textк текстовому представлению счёта. Присвойте атрибутуtextзначениеscoreViewModel.score. Посколькуscore— это целое число, преобразуйте его в строку с помощьюString.valueOf().
<TextView
android:id="@+id/score_text"
...
android:text="@{String.valueOf(scoreViewModel.score)}"
... />- В
ScoreFragmentпосле инициализацииscoreViewModelустановите текущую активность в качестве владельца жизненного цикла переменнойbinding.
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- В
ScoreFragmentудалите наблюдателя для объектаscore.
Код для удаления:
// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})- Запустите приложение и играйте. Обратите внимание, что счёт во фрагменте счёта отображается корректно, без наблюдателя во фрагменте счёта.
Шаг 3: Добавьте форматирование строки с привязкой данных
В макете можно добавить форматирование строк и привязку данных. В этой задаче вы форматируете текущее слово, заключая его в кавычки. Также вы форматируете строку с результатом, добавляя к ней префикс «Current Score» , как показано на следующем рисунке.

- В
string.xmlдобавьте следующие строки, которые будут использоваться для форматирования текстовых представленийwordиscore.%sи%d— это заполнители для текущего слова и текущего результата.
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>- В
game_fragment.xmlобновите атрибутtextтекстового представленияword_text, чтобы использовать строковый ресурсquote_format. ПередайтеgameViewModel.word. Текущее слово будет передано в качестве аргумента в строку форматирования.
<TextView
android:id="@+id/word_text"
...
android:text="@{@string/quote_format(gameViewModel.word)}"
... />- Отформатируйте текстовое представление
scoreаналогичноword_text. В файлеgame_fragment.xmlдобавьте атрибутtextк текстовому представлениюscore_text. Используйте строковый ресурсscore_format, который принимает один числовой аргумент, представленный заполнителем%d. Передайте объектLiveDatascoreв качестве аргумента этой строке форматирования.
<TextView
android:id="@+id/score_text"
...
android:text="@{@string/score_format(gameViewModel.score)}"
... />- В классе
GameFragment, внутри методаonCreateView(), удалите код наблюдателяscore.
Код для удаления:
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})- Очистите, пересоберите и запустите приложение, а затем играйте. Обратите внимание, что текущее слово и счёт отображаются на экране игры.

Поздравляем! Вы интегрировали LiveData и ViewModel с привязкой данных в своё приложение. Это позволяет представлениям в макете напрямую взаимодействовать с ViewModel , без использования обработчиков щелчков во фрагменте. Вы также использовали объекты LiveData в качестве источника привязки данных для автоматического уведомления пользовательского интерфейса об изменениях данных без использования методов наблюдателя LiveData .
Проект Android Studio: GuessTheWord
- Библиотека привязки данных без проблем работает с компонентами архитектуры Android, такими как
ViewModelиLiveData. - Макеты в вашем приложении могут быть привязаны к данным в компонентах архитектуры, которые уже помогают вам управлять жизненным циклом контроллера пользовательского интерфейса и уведомлять об изменениях в данных.
Привязка данных ViewModel
- Вы можете связать
ViewModelс макетом, используя привязку данных. - Объекты
ViewModelсодержат данные пользовательского интерфейса. Передавая объектыViewModelв привязку данных, можно автоматизировать часть взаимодействия между представлениями и объектамиViewModel.
Как связать ViewModel с макетом:
- В файле макета добавьте переменную привязки данных типа
ViewModel.
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>- В файле
GameFragmentпередайтеGameViewModelв привязку данных.
binding.gameViewModel = viewModelПривязки прослушивателя
- Привязки прослушивателя — это выражения привязки в макете, которые запускаются при возникновении событий щелчка, таких как
onClick(). - Привязки прослушивателя записываются в виде лямбда-выражений.
- Используя привязки прослушивателей, вы заменяете прослушиватели щелчков в контроллерах пользовательского интерфейса привязками прослушивателей в файле макета.
- Привязка данных создает прослушиватель и устанавливает его в представлении.
android:onClick="@{() -> gameViewModel.onSkip()}"Добавление LiveData к привязке данных
- Объекты
LiveDataможно использовать в качестве источника привязки данных для автоматического уведомления пользовательского интерфейса об изменениях в данных. - Вы можете привязать представление непосредственно к объекту
LiveDataвViewModel. При измененииLiveDataвViewModelпредставления в макете могут обновляться автоматически, без использования методов наблюдателя в контроллерах пользовательского интерфейса.
android:text="@{gameViewModel.word}"- Чтобы заработала привязка данных
LiveData, установите текущую активность (контроллер пользовательского интерфейса) в качестве владельца жизненного цикла переменнойbindingв контроллере пользовательского интерфейса.
binding.lifecycleOwner = thisФорматирование строк с привязкой данных
- Используя привязку данных, вы можете отформатировать строковый ресурс с помощью заполнителей, например
%sдля строк и%dдля целых чисел. - Чтобы обновить
textатрибут представления, передайте объектLiveDataв качестве аргумента строки форматирования.
android:text="@{@string/quote_format(gameViewModel.word)}"Курс Udacity:
Документация для разработчиков Android:
В этом разделе перечислены возможные домашние задания для студентов, работающих над этой лабораторной работой в рамках курса, проводимого преподавателем. Преподаватель должен выполнить следующие действия:
- При необходимости задавайте домашнее задание.
- Объясните учащимся, как следует сдавать домашние задания.
- Оцените домашние задания.
Преподаватели могут использовать эти предложения так часто или редко, как пожелают, и могут свободно задавать любые другие домашние задания, которые они сочтут подходящими.
Если вы работаете с этой лабораторной работой самостоятельно, можете использовать эти домашние задания для проверки своих знаний.
Ответьте на эти вопросы
Вопрос 1
Какое из следующих утверждений неверно в отношении привязок прослушивателя?
- Привязки прослушивателя — это выражения привязки, которые выполняются при возникновении события.
- Привязки прослушивателя работают со всеми версиями плагина Android Gradle.
- Привязки прослушивателя записываются в виде лямбда-выражений.
- Привязки прослушивателей похожи на ссылки на методы, но они позволяют запускать произвольные выражения привязки данных.
Вопрос 2
Предположим, что ваше приложение включает этот строковый ресурс:
<string name="generic_name">Hello %s</string>
Какой из приведенных ниже вариантов синтаксиса является правильным для форматирования строки с использованием выражения привязки данных?
-
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}"
Вопрос 3
Когда вычисляется и выполняется выражение привязки прослушивателя?
- При изменении данных, хранящихся в
LiveData - Когда действие создается заново из-за изменения конфигурации
- Когда происходит событие, такое как
onClick() - Когда активность уходит на второй план
Начните следующий урок:
Ссылки на другие практические занятия по этому курсу см. на целевой странице практических занятий по основам Android Kotlin .


