Эта практическая работа входит в курс «Основы 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
удалите наблюдателя дляword
LiveData
.
Код для удаления:
/** 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
. Передайте объектLiveData
score
в качестве аргумента этой строке форматирования.
<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 .