Эта кодовая лаборатория является частью курса Android Kotlin Fundamentals. Вы получите максимальную отдачу от этого курса, если будете последовательно работать с лабораториями кода. Все кодовые лаборатории курса перечислены на целевой странице кодовых лабораторий Android Kotlin Fundamentals .
Титульный экран | Экран игры | Экран счета |
Введение
В этой лаборатории кода вы узнаете об одном из компонентов архитектуры Android, ViewModel
:
- Вы используете класс
ViewModel
для хранения и управления данными, связанными с пользовательским интерфейсом, с учетом жизненного цикла. КлассViewModel
позволяет данным сохраняться после изменений конфигурации устройства, таких как повороты экрана и изменения доступности клавиатуры. - Вы используете класс
ViewModelFactory
для создания экземпляра и возврата объектаViewModel
, который выдерживает изменения конфигурации.
Что вы уже должны знать
- Как создавать базовые приложения для Android на Kotlin.
- Как использовать навигационный граф для реализации навигации в вашем приложении.
- Как добавить код для навигации между пунктами назначения вашего приложения и передачи данных между пунктами назначения навигации.
- Как работают жизненные циклы активности и фрагмента.
- Как добавить информацию журнала в приложение и читать журналы с помощью Logcat в Android Studio.
Что вы узнаете
- Как использовать рекомендуемую архитектуру приложений для Android.
- Как использовать классы
Lifecycle
,ViewModel
иViewModelFactory
в вашем приложении. - Как сохранить данные пользовательского интерфейса при изменении конфигурации устройства.
- Что такое шаблон проектирования фабричный метод и как его использовать.
- Как создать объект
ViewModel
с помощью интерфейсаViewModelProvider.Factory
.
Что ты будешь делать
- Добавьте
ViewModel
в приложение, чтобы сохранить данные приложения, чтобы данные сохранялись при изменениях конфигурации. - Используйте
ViewModelFactory
и шаблон проектирования фабричный метод для создания экземпляра объектаViewModel
с параметрами конструктора.
В лабораториях кода Урока 5 вы разрабатываете приложение GuessTheWord, начиная с начального кода. GuessTheWord — это игра в стиле шарад для двух игроков, в которой игроки сотрудничают, чтобы набрать как можно больше очков.
Первый игрок смотрит на слова в приложении и разыгрывает каждое из них по очереди, стараясь не показывать слово второму игроку. Второй игрок пытается угадать слово.
Чтобы начать игру, первый игрок открывает приложение на устройстве и видит слово, например «гитара», как показано на снимке экрана ниже.
Первый игрок разыгрывает слово, стараясь не произносить само слово.
- Когда второй игрок угадывает слово правильно, первый игрок нажимает кнопку « Понял », которая увеличивает счет на единицу и показывает следующее слово.
- Если второй игрок не может угадать слово, первый игрок нажимает кнопку « Пропустить », которая уменьшает счет на единицу и переходит к следующему слову.
- Чтобы завершить игру, нажмите кнопку End Game . (Эта функциональность отсутствует в начальном коде для первой кодовой лаборатории в серии.)
В этой задаче вы загружаете и запускаете начальное приложение и изучаете код.
Шаг 1: Начните
- Загрузите стартовый код GuessTheWord и откройте проект в Android Studio.
- Запустите приложение на устройстве под управлением Android или в эмуляторе.
- Коснитесь кнопок. Обратите внимание, что кнопка « Пропустить » отображает следующее слово и уменьшает оценку на единицу, а кнопка « Понятно » показывает следующее слово и увеличивает оценку на единицу. Кнопка « Завершить игру» не реализована, поэтому при ее нажатии ничего не происходит.
Шаг 2. Пошаговое руководство по коду
- В Android Studio изучите код, чтобы понять, как работает приложение.
- Обязательно просмотрите файлы, описанные ниже, которые особенно важны.
MainActivity.kt
Этот файл содержит только код по умолчанию, сгенерированный шаблоном.
рез/макет/main_activity.xml
Этот файл содержит основной макет приложения. NavHostFragment
размещает другие фрагменты, когда пользователь перемещается по приложению.
Фрагменты пользовательского интерфейса
Стартовый код состоит из трех фрагментов в трех разных пакетах в пакете com.example.android.guesstheword.screens
:
-
title/TitleFragment
для титульного экрана -
game/GameFragment
для игрового экрана -
score/ScoreFragment
для экрана счета
экраны/название/TitleFragment.kt
Фрагмент заголовка — это первый экран, который отображается при запуске приложения. Обработчик кликов устанавливается на кнопку « Играть » для перехода к игровому экрану.
экраны/игра/GameFragment.kt
Это основной фрагмент, где происходит большая часть действия игры:
- Переменные определены для текущего слова и текущего счета.
- Список слов, определенный в методе
wordList
resetList()
, представляет собой пример списка слов, которые будут использоваться в игре. - Метод
onSkip()
является обработчиком нажатия кнопки « Пропустить ». Он уменьшает оценку на 1, а затем отображает следующее слово с помощьюnextWord()
. - Метод
onCorrect()
является обработчиком нажатия кнопки Got It . Этот метод реализован аналогичноonSkip()
. Единственное отличие состоит в том, что этот метод добавляет 1 к счету, а не вычитает.
экраны/оценка/ScoreFragment.kt
ScoreFragment
— это последний экран в игре, на котором отображается окончательный счет игрока. В этой кодовой лаборатории вы добавляете реализацию, чтобы отобразить этот экран и показать окончательную оценку.
рез/навигация/main_navigation.xml
Граф навигации показывает, как фрагменты связаны через навигацию:
- От фрагмента заголовка пользователь может перейти к фрагменту игры.
- Из фрагмента игры пользователь может перейти к фрагменту счета.
- От фрагмента счета пользователь может вернуться к фрагменту игры.
В этом задании вы обнаружите проблемы со стартовым приложением GuessTheWord.
- Запустите начальный код и сыграйте в игру, произнеся несколько слов, нажимая « Пропустить » или « Понятно » после каждого слова.
- Экран игры теперь показывает слово и текущий счет. Измените ориентацию экрана, повернув устройство или эмулятор. Обратите внимание, что текущий счет потерян.
- Запустите игру, набрав еще несколько слов. Когда на игровом экране отображается какой-то счет, закройте и снова откройте приложение. Обратите внимание, что игра перезапускается с самого начала, потому что состояние приложения не сохраняется.
- Сыграйте в игру, произнеся несколько слов, затем нажмите кнопку « Завершить игру» . Обратите внимание, что ничего не происходит.
Проблемы в приложении:
- Стартовое приложение не сохраняет и не восстанавливает состояние приложения во время изменений конфигурации, например при изменении ориентации устройства или при завершении работы и перезапуске приложения.
Вы можете решить эту проблему, используя обратный вызовonSaveInstanceState()
. Однако использованиеonSaveInstanceState()
требует написания дополнительного кода для сохранения состояния в пакете и реализации логики для извлечения этого состояния. Кроме того, объем данных, которые можно сохранить, минимален. - Экран игры не переходит к экрану счета, когда пользователь нажимает кнопку « Завершить игру» .
Вы можете решить эти проблемы, используя компоненты архитектуры приложения , о которых вы узнаете в этой кодовой лаборатории.
Архитектура приложения
Архитектура приложений — это способ проектирования классов ваших приложений и взаимосвязей между ними таким образом, чтобы код был организован, хорошо работал в определенных сценариях и с ним было легко работать. В этом наборе из четырех лабораторий кода улучшения, которые вы вносите в приложение GuessTheWord, следуют рекомендациям по архитектуре приложений Android , и вы используете компоненты архитектуры Android . Архитектура приложения для Android похожа на архитектурный шаблон MVVM (model-view-viewmodel).
Приложение GuessTheWord следует принципу разделения задач и разделено на классы, каждый из которых отвечает за отдельную задачу. В этой первой кодовой лаборатории урока классы, с которыми вы работаете, — это контроллер пользовательского интерфейса, ViewModel
и ViewModelFactory
.
Контроллер пользовательского интерфейса
Контроллер пользовательского интерфейса — это класс на основе пользовательского интерфейса, такой как Activity
или Fragment
. Контроллер пользовательского интерфейса должен содержать только логику, которая обрабатывает взаимодействие пользовательского интерфейса и операционной системы, например отображение представлений и захват пользовательского ввода. Не помещайте логику принятия решений, например логику, определяющую отображаемый текст, в контроллер пользовательского интерфейса.
В начальном коде 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
, а не каждый раз при создании фрагмента. Вы можете удалить оператор журнала в блоке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
, фрагмент игры больше не может получить к нему доступ.
ВGameFragment
вonSkip()
иonCorrect()
замените вызов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() {
}
- В
GameFragment
внутриonCreateView()
найдите код, устанавливающий прослушиватели кликов для кнопок Got It и Skip . Прямо под этими двумя строками установите прослушиватель кликов для кнопки « Завершить игру» . Используйте переменную привязки,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
для фрагмента партитуры. - Расширьте класс
ScoreViewModel
отViewModel.
Добавьте параметр конструктора для окончательной оценки. Добавьте блокinit
с оператором журнала. - В классе
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 рекомендуется разделять классы с разными обязанностями.
- Контроллер пользовательского интерфейса — это класс на основе пользовательского интерфейса, такой как
Activity
илиFragment
. Контроллеры пользовательского интерфейса должны содержать только логику, которая обрабатывает взаимодействие пользовательского интерфейса и операционной системы; они не должны содержать данные для отображения в пользовательском интерфейсе. Поместите эти данные вViewModel
. - Класс
ViewModel
хранит данные, связанные с пользовательским интерфейсом, и управляет ими. КлассViewModel
позволяет данным сохраняться после изменений конфигурации, таких как повороты экрана. -
ViewModel
является одним из рекомендуемых компонентов архитектуры Android . -
ViewModelProvider.Factory
— это интерфейс, который можно использовать для создания объектаViewModel
.
В таблице ниже сравниваются контроллеры пользовательского интерфейса с экземплярами ViewModel
, которые содержат для них данные:
Контроллер пользовательского интерфейса | ViewModel |
Примером контроллера пользовательского интерфейса является | Примером |
Не содержит данных для отображения в пользовательском интерфейсе. | Содержит данные, которые контроллер пользовательского интерфейса отображает в пользовательском интерфейсе. |
Содержит код для отображения данных и код пользовательских событий, таких как прослушиватели кликов. | Содержит код для обработки данных. |
Уничтожается и создается заново при каждом изменении конфигурации. | Уничтожается только тогда, когда связанный контроллер пользовательского интерфейса исчезает навсегда — для действия, когда действие завершается, или для фрагмента, когда фрагмент отсоединяется. |
Содержит представления. | Никогда не должен содержать ссылок на действия, фрагменты или представления, потому что они не сохраняются после изменений конфигурации, а |
Содержит ссылку на связанную | Не содержит ссылок на связанный контроллер пользовательского интерфейса. |
Удасити курс:
Документация для разработчиков Android:
- Обзор ViewModel
- Управление жизненными циклами с помощью компонентов, поддерживающих жизненный цикл
- Руководство по архитектуре приложения
-
ViewModelProvider
-
ViewModelProvider.Factory
Другой:
- Архитектурный шаблон MVVM (model-view-viewmodel).
- Принцип разделения задач (SoC)
- Шаблон фабричного метода
В этом разделе перечислены возможные домашние задания для студентов, которые работают с этой кодовой лабораторией в рамках курса, проводимого инструктором. Инструктор должен сделать следующее:
- При необходимости задайте домашнее задание.
- Объясните учащимся, как сдавать домашние задания.
- Оценивайте домашние задания.
Преподаватели могут использовать эти предложения так мало или так часто, как они хотят, и должны свободно давать любые другие домашние задания, которые они считают подходящими.
Если вы работаете с этой кодовой лабораторией самостоятельно, не стесняйтесь использовать эти домашние задания, чтобы проверить свои знания.
Ответьте на эти вопросы
Вопрос 1
Чтобы избежать потери данных во время изменения конфигурации устройства, в каком классе следует сохранять данные приложения?
-
ViewModel
-
LiveData
-
Fragment
-
Activity
вопрос 2
ViewModel
никогда не должна содержать никаких ссылок на фрагменты, действия или представления. Правда или ложь?
- Истинный
- ЛОЖЬ
Вопрос 3
Когда уничтожается ViewModel
?
- Когда связанный контроллер пользовательского интерфейса уничтожается и воссоздается во время изменения ориентации устройства.
- В изменении ориентации.
- Когда связанный контроллер пользовательского интерфейса завершен (если это действие) или отсоединен (если это фрагмент).
- Когда пользователь нажимает кнопку «Назад».
Вопрос 4
Для чего нужен интерфейс ViewModelFactory
?
- Создание экземпляра объекта
ViewModel
. - Сохранение данных при изменении ориентации.
- Обновление данных, отображаемых на экране.
- Получение уведомлений при изменении данных приложения.
Начать следующий урок:
Ссылки на другие лаборатории кода в этом курсе см. на целевой странице лаборатории кода Android Kotlin Fundamentals .