Эта практическая работа входит в курс «Основы Android Kotlin». Вы получите максимальную пользу от этого курса, если будете выполнять практические работы последовательно. Все практические работы курса перечислены на целевой странице практической работы «Основы Android Kotlin» .
Титульный экран |
Игровой экран |
Экран результатов |
Введение
В этой лабораторной работе вы познакомитесь с одним из компонентов архитектуры Android — ViewModel :
- Класс
ViewModelиспользуется для хранения и управления данными пользовательского интерфейса с учётом жизненного цикла. КлассViewModelпозволяет данным сохраняться при изменениях конфигурации устройства, таких как поворот экрана и изменение доступности клавиатуры. - Класс
ViewModelFactoryиспользуется для создания и возврата объектаViewModel, который сохраняется при изменении конфигурации.
Что вам уже следует знать
- Как создавать простые приложения для Android на Kotlin.
- Как использовать навигационный граф для реализации навигации в вашем приложении.
- Как добавить код для навигации между пунктами назначения вашего приложения и передачи данных между пунктами назначения навигации.
- Как работают жизненные циклы активности и фрагмента.
- Как добавить информацию журнала в приложение и читать журналы с помощью Logcat в Android Studio.
Чему вы научитесь
- Как использовать рекомендуемую архитектуру приложений Android.
- Как использовать классы
Lifecycle,ViewModelиViewModelFactoryв вашем приложении. - Как сохранить данные пользовательского интерфейса при изменении конфигурации устройства.
- Что такое шаблон проектирования «Фабричный метод» и как его использовать.
- Как создать объект
ViewModelс помощью интерфейсаViewModelProvider.Factory.
Что ты будешь делать?
- Добавьте
ViewModelв приложение, чтобы сохранить данные приложения и сохранить их при изменении конфигурации. - Используйте
ViewModelFactoryи шаблон проектирования «фабричный метод» для создания экземпляра объектаViewModelс параметрами конструктора.
В практических занятиях по коду Урока 5 вы разработаете приложение GuessTheWord, начав с базового кода. GuessTheWord — это игра в стиле шарады для двух игроков, где игроки объединяют усилия, чтобы набрать как можно больше очков.
Первый игрок смотрит на слова в приложении и по очереди разыгрывает каждое слово, не показывая его второму игроку. Второй игрок пытается угадать слово.
Чтобы начать игру, первый игрок открывает приложение на устройстве и видит слово, например, «гитара», как показано на снимке экрана ниже.
Первый игрок изображает слово, стараясь не произносить его целиком.
- Когда второй игрок правильно отгадывает слово, первый игрок нажимает кнопку « Понял» , что увеличивает счет на единицу и показывает следующее слово.
- Если второй игрок не может угадать слово, первый игрок нажимает кнопку «Пропустить» , что уменьшает счет на единицу и переходит к следующему слову.
- Чтобы завершить игру, нажмите кнопку «Завершить игру» . (Эта функция отсутствует в стартовом коде первой лабораторной работы в серии.)
В этом задании вы загрузите и запустите стартовое приложение и изучите код.
Шаг 1: Начало работы
- Загрузите стартовый код GuessTheWord и откройте проект в Android Studio.
- Запустите приложение на устройстве Android или на эмуляторе.
- Нажмите на кнопки. Обратите внимание, что кнопка «Пропустить» отображает следующее слово и уменьшает счёт на единицу, а кнопка « Понял» отображает следующее слово и увеличивает счёт на единицу. Кнопка «Завершить игру» не реализована, поэтому при её нажатии ничего не происходит.
Шаг 2: Проведите пошаговый разбор кода
- В Android Studio изучите код, чтобы понять, как работает приложение.
- Обязательно ознакомьтесь с файлами, описанными ниже, они особенно важны.
MainActivity.kt
Этот файл содержит только код по умолчанию, сгенерированный шаблоном.
res/layout/main_activity.xml
Этот файл содержит основной макет приложения. NavHostFragment размещает остальные фрагменты по мере перемещения пользователя по приложению.
Фрагменты пользовательского интерфейса
Стартовый код состоит из трех фрагментов в трех различных пакетах в пакете com.example.android.guesstheword.screens :
-
title/TitleFragmentдля титульного экрана -
game/GameFragmentдля игрового экрана -
score/ScoreFragmentдля экрана счета
экраны/title/TitleFragment.kt
Заголовочный фрагмент — это первый экран, отображаемый при запуске приложения. Обработчик нажатия установлен на кнопку «Играть» для перехода к экрану игры.
screens/game/GameFragment.kt
Это основной фрагмент, где происходит большая часть действия игры:
- Переменные определяются для текущего слова и текущего счета.
- Список
wordList, определенный внутри методаresetList()представляет собой пример списка слов, которые будут использоваться в игре. - Метод
onSkip()— это обработчик нажатия кнопки «Пропустить» . Он уменьшает счёт на 1, а затем отображает следующее слово с помощью методаnextWord(). - Метод
onCorrect()— это обработчик нажатия кнопки « Понял» . Этот метод реализован аналогично методуonSkip(). Единственное отличие заключается в том, что этот метод добавляет 1 к счёту, а не вычитает.
screens/score/ScoreFragment.kt
ScoreFragment — это финальный экран в игре, отображающий итоговый счёт игрока. В этой лабораторной работе вы добавите реализацию для отображения этого экрана и итогового счёта.
res/navigation/main_navigation.xml
Граф навигации показывает, как фрагменты связаны посредством навигации:
- Из фрагмента заголовка пользователь может перейти к фрагменту игры.
- Из игрового фрагмента пользователь может перейти к фрагменту с результатами.
- Из фрагмента счета пользователь может вернуться к фрагменту игры.
В этом задании вы обнаружили проблемы с начальным приложением GuessTheWord.
- Запустите стартовый код и пройдите игру, нажав « Пропустить» или «Понятно» после каждого слова.
- На экране игры теперь отображается слово и текущий счёт. Измените ориентацию экрана, повернув устройство или эмулятор. Обратите внимание, что текущий счёт теряется.
- Пропустите игру ещё несколько слов. Когда на экране появится игровой счёт, закройте и снова откройте приложение. Обратите внимание, что игра перезапустится с самого начала, поскольку состояние приложения не сохраняется.
- Пройдите игру, произнеся несколько слов, а затем нажмите кнопку «Завершить игру» . Обратите внимание: ничего не происходит.
Проблемы в приложении:
- Стартовое приложение не сохраняет и не восстанавливает состояние приложения при изменении конфигурации, например, при изменении ориентации устройства или при завершении работы и перезапуске приложения.
Эту проблему можно решить с помощью обратного вызоваonSaveInstanceState(). Однако использование методаonSaveInstanceState()требует написания дополнительного кода для сохранения состояния в пакете и реализации логики для его извлечения. Кроме того, объём хранимых данных минимален. - Игровой экран не переходит к экрану счета, когда пользователь нажимает кнопку «Завершить игру» .
Вы можете решить эти проблемы, используя компоненты архитектуры приложения , о которых вы узнаете в этой лабораторной работе.
Архитектура приложения
Архитектура приложения — это способ проектирования классов приложения и взаимосвязей между ними, обеспечивающий организованность кода, его эффективность в определённых сценариях и удобство работы с ним. В этом наборе из четырёх практических работ вы внесёте улучшения в приложение GuessTheWord, следуя рекомендациям по архитектуре приложений для Android , используя компоненты архитектуры Android . Архитектура приложения для Android аналогична архитектурному шаблону MVVM (модель-представление-модель представления).
Приложение GuessTheWord следует принципу разделения ответственности и разделено на классы, каждый из которых отвечает за отдельную задачу. В этой первой практической работе урока вы будете работать с классами UI-контроллера, ViewModel и ViewModelFactory .
Контроллер пользовательского интерфейса
Контроллер пользовательского интерфейса (UI) — это класс, основанный на пользовательском интерфейсе, такой как Activity или Fragment . UI-контроллер должен содержать только логику, обрабатывающую взаимодействие с пользовательским интерфейсом и операционной системой, например, отображение представлений и захват пользовательского ввода. Не помещайте в UI-контроллер логику принятия решений, например, логику, определяющую отображаемый текст.
В стартовом коде GuessTheWord контроллерами пользовательского интерфейса являются три фрагмента: GameFragment , ScoreFragment, и TitleFragment . Следуя принципу «разделения ответственности», GameFragment отвечает только за отрисовку игровых элементов на экране и распознавание нажатий кнопок пользователем, и ничего более. Когда пользователь нажимает кнопку, эта информация передаётся в GameViewModel .
ViewModel
ViewModel хранит данные, которые будут отображены во фрагменте или активности, связанной с ViewModel . ViewModel может выполнять простые вычисления и преобразования данных для подготовки их к отображению контроллером пользовательского интерфейса. В этой архитектуре ViewModel принимает решения.
GameViewModel хранит такие данные, как значение счёта, список слов и текущее слово, поскольку именно эти данные будут отображаться на экране. GameViewModel также содержит бизнес-логику для выполнения простых вычислений, позволяющих определить текущее состояние данных.
ViewModelFactory
ViewModelFactory создает экземпляры объектов ViewModel с параметрами конструктора или без них.

В последующих практических занятиях вы узнаете о других компонентах архитектуры Android, связанных с контроллерами пользовательского интерфейса и ViewModel .
Класс ViewModel предназначен для хранения и управления данными пользовательского интерфейса. В этом приложении каждый ViewModel связан с одним фрагментом.
В этом задании вы добавите в приложение свою первую ViewModel — GameViewModel для GameFragment . Вы также узнаете, что означает, что ViewModel учитывает жизненный цикл.
Шаг 1: Добавьте класс GameViewModel
- Откройте файл
build.gradle(module:app). В блокеdependenciesдобавьте зависимость Gradle дляViewModel.
Если вы используете последнюю версию библиотеки, приложение-решение должно скомпилироваться как положено. Если это не так, попробуйте решить проблему самостоятельно или вернитесь к версии, указанной ниже.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'- В папке
screens/game/создайте новый класс Kotlin с именемGameViewModel. - Сделайте так, чтобы класс
GameViewModelрасширял абстрактный классViewModel. - Чтобы лучше понять, как
ViewModelучитывает жизненный цикл, добавьте блокinitс операторомlog.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}Шаг 2: Переопределите onCleared() и добавьте ведение журнала
ViewModel уничтожается при отсоединении связанного фрагмента или при завершении активности. Непосредственно перед уничтожением ViewModel вызывается метод обратного вызова onCleared() для очистки ресурсов.
- В классе
GameViewModelпереопределите методonCleared(). - Добавьте оператор журнала в
onCleared()для отслеживания жизненного циклаGameViewModel.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}Шаг 3: Свяжите GameViewModel с фрагментом игры
ViewModel необходимо связать с контроллером пользовательского интерфейса. Для этого необходимо создать ссылку на ViewModel внутри контроллера пользовательского интерфейса.
На этом этапе вы создаете ссылку на GameViewModel внутри соответствующего контроллера пользовательского интерфейса, которым является GameFragment .
- В классе
GameFragmentдобавьте поле типаGameViewModelна верхнем уровне в качестве переменной класса.
private lateinit var viewModel: GameViewModelШаг 4: Инициализация ViewModel
При изменении конфигурации, например, при повороте экрана, контроллеры пользовательского интерфейса, такие как фрагменты, создаются заново. Однако экземпляры ViewModel сохраняются. Если вы создаёте экземпляр ViewModel с помощью класса ViewModel , каждый раз при повторном создании фрагмента создаётся новый объект. Вместо этого создайте экземпляр ViewModel с помощью ViewModelProvider .

Как работает ViewModelProvider :
-
ViewModelProviderвозвращает существующуюViewModel, если она существует, или создает новую, если она еще не существует. -
ViewModelProviderсоздает экземплярViewModelв связи с заданной областью действия (действием или фрагментом). - Созданная
ViewModelсохраняется до тех пор, пока существует область действия. Например, если область действия представляет собой фрагмент,ViewModelсохраняется до тех пор, пока фрагмент не будет отсоединен.
Инициализируйте ViewModel , используя метод ViewModelProviders.of() для создания ViewModelProvider :
- В классе
GameFragmentинициализируйте переменнуюviewModel. Поместите этот код вonCreateView()после определения переменной привязки. Используйте методViewModelProviders.of()и передайте ему связанный контекстGameFragmentи классGameViewModel. - Над инициализацией объекта
ViewModelдобавьте оператор журнала для регистрации вызова методаViewModelProviders.of().
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)- Запустите приложение. В Android Studio откройте панель Logcat и выберите фильтр по параметру
Game. Нажмите кнопку «Воспроизвести» на устройстве или эмуляторе. Откроется экран игры.
Как показано в Logcat, методonCreateView()объектаGameFragmentвызывает методViewModelProviders.of()для созданияGameViewModel. Операторы журналирования, добавленные вGameFragmentиGameViewModelотображаются в Logcat.

- Включите функцию автоматического поворота на устройстве или эмуляторе и несколько раз измените ориентацию экрана.
GameFragmentкаждый раз уничтожается и создаётся заново, поэтомуViewModelProviders.of()вызывается каждый раз. ОднакоGameViewModelсоздаётся только один раз и не пересоздаётся и не уничтожается при каждом вызове.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- Выйти из игры или выйти из игрового фрагмента.
GameFragmentуничтожается. Связанная с нимGameViewModelтакже уничтожается, и вызывается метод обратного вызоваonCleared().
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
ViewModel сохраняет работоспособность при изменении конфигурации, поэтому это хорошее место для данных, которым необходимо сохранять работоспособность при изменении конфигурации:
- Поместите данные, которые необходимо отобразить на экране, и код для обработки этих данных в
ViewModel. -
ViewModelникогда не должен содержать ссылок на фрагменты, действия или представления, поскольку действия, фрагменты и представления не сохраняются при изменении конфигурации.

Для сравнения, вот как обрабатываются данные пользовательского интерфейса GameFragment в стартовом приложении до добавления ViewModel и после добавления ViewModel :
- Перед добавлением
ViewModel:
При изменении конфигурации приложения, например, повороте экрана, фрагмент игры уничтожается и создаётся заново. Данные теряются. - После добавления
ViewModelи перемещения данных пользовательского интерфейса игрового фрагмента воViewModel:
Все данные, которые фрагмент должен отображать, теперь находятся вViewModel. При изменении конфигурации приложенияViewModelсохраняется, и данные сохраняются.

В этой задаче вы перенесете данные пользовательского интерфейса приложения в класс GameViewModel вместе с методами их обработки. Это позволит сохранить данные при изменении конфигурации.
Шаг 1: Перемещение полей данных и обработки данных во ViewModel
Переместите следующие поля данных и методы из GameFragment в GameViewModel :
- Переместите поля данных «
word,scoreиwordList. Убедитесь, что поляwordиscoreне являютсяprivate.
Не перемещайте переменную привязкиGameFragmentBinding, поскольку она содержит ссылки на представления. Эта переменная используется для расширения макета, настройки обработчиков щелчков и отображения данных на экране — это входит в обязанности фрагмента. - Переместите методы
resetList()иnextWord(). Эти методы определяют, какое слово отображать на экране. - Изнутри метода
onCreateView()переместите вызовы методовresetList()иnextWord()в блокinitобъектаGameViewModel.
Эти методы должны находиться в блокеinit, поскольку список слов следует сбрасывать при созданииViewModel, а не при каждом создании фрагмента. Вы можете удалить оператор log в блокеinitобъектаGameFragment.
Обработчики нажатий onSkip() и onCorrect() в GameFragment содержат код для обработки данных и обновления пользовательского интерфейса. Код обновления пользовательского интерфейса должен остаться во фрагменте, но код обработки данных необходимо перенести во ViewModel .
На данный момент поместите в обоих местах одинаковые методы:
- Скопируйте методы
onSkip()иonCorrect()изGameFragmentвGameViewModel. - В
GameViewModelубедитесь, что методыonSkip()иonCorrect()не являютсяprivate, так как вы будете ссылаться на эти методы из фрагмента.
Вот код класса GameViewModel после рефакторинга:
class GameViewModel : ViewModel() {
// The current word
var word = ""
// The current score
var score = 0
// The list of words - the front of the list is the next word to guess
private lateinit var wordList: MutableList<String>
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
init {
resetList()
nextWord()
Log.i("GameViewModel", "GameViewModel created!")
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word = wordList.removeAt(0)
}
updateWordText()
updateScoreText()
}
/** Methods for buttons presses **/
fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
} Вот код класса GameFragment после рефакторинга:
/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var viewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
updateScoreText()
updateWordText()
return binding.root
}
/** Methods for button click handlers **/
private fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}Шаг 2: Обновите ссылки на обработчики щелчков и поля данных в GameFragment.
- В
GameFragmentобновите методыonSkip()иonCorrect(). Удалите код обновления счёта и вместо этого вызовите соответствующие методыonSkip()иonCorrect()вviewModel. - Поскольку вы переместили метод
nextWord()вViewModel, фрагмент игры больше не может получить к нему доступ.
В методахonSkip()иonCorrect()GameFragmentзамените вызовnextWord()наupdateScoreText()иupdateWordText(). Эти методы выводят данные на экран.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}- В
GameFragmentобновите переменныеscoreиword, чтобы использовать переменныеGameViewModel, поскольку эти переменные теперь находятся вGameViewModel.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}- В
GameViewModel, внутри методаnextWord(), удалите вызовы методовupdateWordText()иupdateScoreText(). Теперь эти методы вызываются изGameFragment. - Соберите приложение и убедитесь в отсутствии ошибок. Если ошибки есть, очистите и пересоберите проект.
- Запустите приложение и сыграйте в игру, используя несколько слов. Находясь на экране игры, поверните устройство. Обратите внимание, что текущий счёт и слово сохраняются после смены ориентации.
Отличная работа! Теперь все данные вашего приложения хранятся в ViewModel , поэтому они сохраняются при изменении конфигурации.
В этой задаче вы реализуете прослушиватель щелчков для кнопки «Завершить игру» .
- В
GameFragmentдобавьте методonEndGame(). МетодonEndGame()будет вызываться, когда пользователь нажмёт кнопку «Завершить игру» .
private fun onEndGame() {
}- В методе
onCreateView()классаGameFragmentнайдите код, который устанавливает обработчики щелчков для кнопок « Понял» и «Пропустить» . Прямо под этими двумя строками установите обработчик щелчков для кнопки «Завершить игру» . Используйте переменную привязкиbinding. Внутри обработчика щелчков вызовите методonEndGame().
binding.endGameButton.setOnClickListener { onEndGame() }- В
GameFragmentдобавьте методgameFinished()для перехода приложения к экрану счёта. Передайте счёт в качестве аргумента с помощью Safe Args .
/**
* Called when the game is finished
*/
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score
NavHostFragment.findNavController(this).navigate(action)
}- В методе
onEndGame()вызовите методgameFinished().
private fun onEndGame() {
gameFinished()
}- Запустите приложение, играйте в игру и циклично перебирайте слова. Нажмите кнопку «Завершить игру» . Обратите внимание, что приложение переходит на экран счёта, но итоговый счёт не отображается. Исправьте это в следующем задании.
|
|
Когда пользователь завершает игру, ScoreFragment не отображает счёт. Вам нужно, чтобы ViewModel хранила счёт, отображаемый ScoreFragment . Значение счёта будет передано во время инициализации ViewModel с помощью шаблона «фабричный метод» .
Шаблон «Фабричный метод» — это порождающий шаблон проектирования , использующий фабричные методы для создания объектов. Фабричный метод — это метод, возвращающий экземпляр того же класса.
В этой задаче вы создадите ViewModel с параметризованным конструктором для фрагмента счета и фабричным методом для создания экземпляра ViewModel .
- В пакете
scoreсоздайте новый класс Kotlin с именемScoreViewModel. Этот класс будетViewModelдля фрагмента score. - Расширьте класс
ScoreViewModelизViewModel.Добавьте параметр конструктора для итоговой оценки. Добавьте блокinitс оператором log. - В классе
ScoreViewModelдобавьте переменную с именемscoreдля сохранения итогового результата.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}- В пакете
scoreсоздайте ещё один класс Kotlin с именемScoreViewModelFactory. Этот класс будет отвечать за создание экземпляра объектаScoreViewModel. - Расширьте класс
ScoreViewModelFactoryотViewModelProvider.Factoryи добавьте параметр конструктора для итоговой оценки.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}- В
ScoreViewModelFactoryAndroid Studio выдаёт ошибку о нереализованном абстрактном члене. Чтобы устранить эту ошибку, переопределите методcreate(). В методеcreate()верните только что созданный объектScoreViewModel.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}- В
ScoreFragmentсоздайте переменные класса дляScoreViewModelиScoreViewModelFactory.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory- В
ScoreFragment, внутриonCreateView(), после инициализации переменнойbinding, инициализируйтеviewModelFactory. ИспользуйтеScoreViewModelFactory. Передайте окончательный результат из набора аргументов в качестве параметра конструктора вScoreViewModelFactory().
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)- В
onCreateView() после инициализацииviewModelFactoryинициализируйте объектviewModel. Вызовите методViewModelProviders.of(), передайте ему контекст фрагмента счёта иviewModelFactory. Это создаст объектScoreViewModelс помощью фабричного метода, определённого в классеviewModelFactory.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)- В методе
onCreateView()после инициализацииviewModelустановите текст представленияscoreTextравным итоговой оценке, определенной вScoreViewModel.
binding.scoreText.text = viewModel.score.toString()- Запустите приложение и играйте. Перебирайте несколько или все слова и нажмите «Завершить игру» . Обратите внимание, что фрагмент счёта теперь отображает итоговый счёт.

- Необязательно: проверьте логи
ScoreViewModelв Logcat, отфильтровав их поScoreViewModel. Значение оценки должно отображаться.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
В этом задании вы реализовали ScoreFragment для использования ViewModel . Вы также узнали, как создать параметризованный конструктор для ViewModel с помощью интерфейса ViewModelFactory .
Поздравляем! Вы изменили архитектуру своего приложения, чтобы использовать один из компонентов архитектуры Android — ViewModel . Вы решили проблему жизненного цикла приложения, и теперь данные игры сохраняются при изменении конфигурации. Вы также узнали, как создать параметризованный конструктор для создания ViewModel с помощью интерфейса ViewModelFactory .
Проект Android Studio: GuessTheWord
- В рекомендациях по архитектуре приложений для Android рекомендуется разделять классы, имеющие разные обязанности.
- Контроллер пользовательского интерфейса (UI Controller) — это класс, основанный на пользовательском интерфейсе, такой как
ActivityилиFragment. Контроллеры UI должны содержать только логику, обрабатывающую взаимодействие с пользовательским интерфейсом и операционной системой; они не должны содержать данные для отображения в пользовательском интерфейсе. Поместите эти данные воViewModel. - Класс
ViewModelхранит и управляет данными, связанными с пользовательским интерфейсом. КлассViewModelпозволяет данным сохраняться при изменении конфигурации, например при повороте экрана. -
ViewModel— один из рекомендуемых компонентов архитектуры Android . -
ViewModelProvider.Factory— это интерфейс, который можно использовать для создания объектаViewModel.
В таблице ниже сравниваются контроллеры пользовательского интерфейса с экземплярами ViewModel , которые хранят для них данные:
Контроллер пользовательского интерфейса | ViewModel |
Примером контроллера пользовательского интерфейса является | Примером |
Не содержит никаких данных для отображения в пользовательском интерфейсе. | Содержит данные, которые контроллер пользовательского интерфейса отображает в пользовательском интерфейсе. |
Содержит код для отображения данных и код пользовательских событий, такой как прослушиватели щелчков. | Содержит код для обработки данных. |
Уничтожается и создается заново при каждом изменении конфигурации. | Уничтожается только тогда, когда связанный с ним контроллер пользовательского интерфейса окончательно исчезает — для активности, когда активность завершается, или для фрагмента, когда фрагмент отсоединяется. |
Содержит представления. | Никогда не должны содержать ссылок на действия, фрагменты или представления, поскольку они не сохраняются при изменении конфигурации, в отличие от |
Содержит ссылку на соответствующую | Не содержит ссылок на соответствующий контроллер пользовательского интерфейса. |
Курс Udacity:
Документация для разработчиков Android:
- Обзор ViewModel
- Управление жизненными циклами с помощью компонентов, поддерживающих жизненный цикл
- Руководство по архитектуре приложения
-
ViewModelProvider -
ViewModelProvider.Factory
Другой:
- Архитектурный шаблон MVVM (модель-представление-модель представления).
- Принцип проектирования разделения ответственности (SoC)
- Шаблон метода фабрики
В этом разделе перечислены возможные домашние задания для студентов, работающих над этой лабораторной работой в рамках курса, проводимого преподавателем. Преподаватель должен выполнить следующие действия:
- При необходимости задавайте домашнее задание.
- Объясните учащимся, как следует сдавать домашние задания.
- Оцените домашние задания.
Преподаватели могут использовать эти предложения так часто или редко, как пожелают, и могут свободно задавать любые другие домашние задания, которые они сочтут подходящими.
Если вы работаете с этой лабораторной работой самостоятельно, можете использовать эти домашние задания для проверки своих знаний.
Ответьте на эти вопросы
Вопрос 1
В каком классе следует сохранять данные приложения, чтобы избежать потери данных при изменении конфигурации устройства?
-
ViewModel -
LiveData -
Fragment -
Activity
Вопрос 2
ViewModel никогда не должен содержать ссылок на фрагменты, действия или представления. Верно или неверно?
- Истинный
- ЛОЖЬ
Вопрос 3
Когда ViewModel уничтожается?
- Когда связанный контроллер пользовательского интерфейса уничтожается и создается заново во время изменения ориентации устройства.
- В смене ориентации.
- Когда связанный контроллер пользовательского интерфейса завершен (если это активность) или отсоединен (если это фрагмент).
- Когда пользователь нажимает кнопку «Назад».
Вопрос 4
Для чего нужен интерфейс ViewModelFactory ?
- Создание объекта
ViewModel. - Сохранение данных при изменении ориентации.
- Обновление данных, отображаемых на экране.
- Получение уведомлений при изменении данных приложения.
Начните следующий урок:
Ссылки на другие практические занятия по этому курсу см. на целевой странице практических занятий по основам Android Kotlin .




