Эта практическая работа входит в курс «Основы 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 {
}
- В
ScoreViewModelFactory
Android 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 .