This codelab is part of the Android Kotlin Fundamentals course. You'll get the most value out of this course if you work through the codelabs in sequence. All the course codelabs are listed on the Android Kotlin Fundamentals codelabs landing page.
Title screen |
Game screen |
Score screen |
Introduction
In this codelab, you learn about one of the Android Architecture Components, ViewModel:
- You use the
ViewModelclass to store and manage UI-related data in a lifecycle-conscious way. TheViewModelclass allows data to survive device-configuration changes such as screen rotations and changes to keyboard availability. - You use the
ViewModelFactoryclass to instantiate and return theViewModelobject that survives configuration changes.
What you should already know
- How to create basic Android apps in Kotlin.
- How to use the navigation graph to implement navigation in your app.
- How to add code to navigate between your app's destinations and pass data between navigation destinations.
- How the activity and fragment lifecycles work.
- How to add logging information to an app and read logs using Logcat in Android Studio.
What you'll learn
- How to use the recommended Android app architecture.
- How to use the
Lifecycle,ViewModel, andViewModelFactoryclasses in your app. - How to retain UI data through device-configuration changes.
- What the factory method design pattern is and how to use it.
- How to create a
ViewModelobject using the interfaceViewModelProvider.Factory.
What you'll do
- Add a
ViewModelto the app, to save app's data so the data survives configuration changes. - Use
ViewModelFactoryand the factory-method design pattern to instantiate aViewModelobject with constructor parameters.
In the Lesson 5 codelabs, you develop the GuessTheWord app, beginning with starter code. GuessTheWord is a two-player charades-style game, where the players collaborate to achieve the highest score possible.
The first player looks at the words in the app and acts each one out in turn, making sure not to show the word to the second player. The second player tries to guess the word.
To play the game, the first player opens the app on the device and sees a word, for example "guitar," as shown in the screenshot below.
The first player acts out the word, being careful not to actually say the word itself.
- When the second player guesses the word correctly, the first player presses the Got It button, which increases the count by one and shows the next word.
- If the second player can't guess the word, the first player presses the Skip button, which decreases the count by one and skips to the next word.
- To end the game, press the End Game button. (This functionality isn't in the starter code for the first codelab in the series.)
In this task, you download and run the starter app and examine the code.
Step 1: Get started
- Download the GuessTheWord starter code and open the project in Android Studio.
- Run the app on an Android-powered device, or on an emulator.
- Tap the buttons. Notice that the Skip button displays the next word and decreases the score by one, and the Got It button shows the next word and increases the score by one. The End Game button is not implemented, so nothing happens when you tap it.
Step 2: Do a code walkthrough
- In Android Studio, explore the code to get a feel for how the app works.
- Make sure to look at the files described below, which are particularly important.
MainActivity.kt
This file contains only default, template-generated code.
res/layout/main_activity.xml
This file contains the app's main layout. The NavHostFragment hosts the other fragments as the user navigates through the app.
UI fragments
The starter code has three fragments in three different packages under the com.example.android.guesstheword.screens package:
title/TitleFragmentfor the title screengame/GameFragmentfor the game screenscore/ScoreFragmentfor the score screen
screens/title/TitleFragment.kt
The title fragment is the first screen that is displayed when the app is launched. A click handler is set to the Play button, to navigate to the game screen.
screens/game/GameFragment.kt
This is the main fragment, where most of the game's action takes place:
- Variables are defined for the current word and the current score.
- The
wordListdefined inside theresetList()method is a sample list of words to be used in the game. - The
onSkip()method is the click handler for the Skip button. It decreases the score by 1, then displays the next word using thenextWord()method. - The
onCorrect()method is the click handler for the Got It button. This method is implemented similarly to theonSkip()method. The only difference is that this method adds 1 to the score instead of subtracting.
screens/score/ScoreFragment.kt
ScoreFragment is the final screen in the game, and it displays the player's final score. In this codelab, you add the implementation to display this screen and show the final score.
res/navigation/main_navigation.xml
The navigation graph shows how the fragments are connected through navigation:
- From the title fragment, the user can navigate to the game fragment.
- From the game fragment, the user can navigate to the score fragment.
- From the score fragment, the user can navigate back to the game fragment.
In this task, you find issues with the GuessTheWord starter app.
- Run the starter code and play the game through a few words, tapping either Skip or Got It after each word.
- The game screen now shows a word and the current score. Change the screen orientation by rotating the device or emulator. Notice that the current score is lost.
- Run the game through a few more words. When the game screen is displayed with some score, close and re-open the app. Notice that the game restarts from the beginning, because the app state is not saved.
- Play the game through a few words, then tap the End Game button. Notice that nothing happens.
Issues in the app:
- The starter app doesn't save and restore the app state during configuration changes, such as when the device orientation changes, or when the app shuts down and restarts.
You could resolve this issue using theonSaveInstanceState()callback. However, using theonSaveInstanceState()method requires you to write extra code to save the state in a bundle, and to implement the logic to retrieve that state. Also, the amount of data that can be stored is minimal. - The game screen does not navigate to the score screen when the user taps the End Game button.
You can resolve these issues using the app architecture components that you learn about in this codelab.
App architecture
App architecture is a way of designing your apps' classes, and the relationships between them, such that the code is organized, performs well in particular scenarios, and is easy to work with. In this set of four codelabs, the improvements that you make to the GuessTheWord app follow the Android app architecture guidelines, and you use Android Architecture Components. The Android app architecture is similar to the MVVM (model-view-viewmodel) architectural pattern.
The GuessTheWord app follows the separation of concerns design principle and is divided into classes, with each class addressing a separate concern. In this first codelab of the lesson, the classes you work with are a UI controller, a ViewModel, and a ViewModelFactory.
UI controller
A UI controller is a UI-based class such as Activity or Fragment. A UI controller should only contain logic that handles UI and operating-system interactions such as displaying views and capturing user input. Don't put decision-making logic, such as logic that determines the text to display, into the UI controller.
In the GuessTheWord starter code, the UI controllers are the three fragments: GameFragment, ScoreFragment, and TitleFragment. Following the "separation of concerns" design principle, the GameFragment is only responsible for drawing game elements to the screen and knowing when the user taps the buttons, and nothing more. When the user taps a button, this information is passed to the GameViewModel.
ViewModel
A ViewModel holds data to be displayed in a fragment or activity associated with the ViewModel. A ViewModel can do simple calculations and transformations on data to prepare the data to be displayed by the UI controller. In this architecture, the ViewModel performs the decision-making.
The GameViewModel holds data like the score value, the list of words, and the current word, because this is the data to be displayed on the screen. The GameViewModel also contains the business logic to perform simple calculations to decide what the current state of the data is.
ViewModelFactory
A ViewModelFactory instantiates ViewModel objects, with or without constructor parameters.

In later codelabs, you learn about other Android Architecture Components that are related to UI controllers and ViewModel.
The ViewModel class is designed to store and manage the UI-related data. In this app, each ViewModel is associated with one fragment.
In this task, you add your first ViewModel to your app, the GameViewModel for the GameFragment. You also learn what it means that the ViewModel is lifecycle-aware.
Step 1: Add the GameViewModel class
- Open the
build.gradle(module:app)file. Inside thedependenciesblock, add the Gradle dependency for theViewModel.
If you use the latest version of the library, the solution app should compile as expected. If it doesn't, try resolving the issue, or revert to the version shown below.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'- In the package
screens/game/folder, create a new Kotlin class calledGameViewModel. - Make the
GameViewModelclass extend the abstract classViewModel. - To help you better understand how the
ViewModelis lifecycle-aware, add aninitblock with alogstatement.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}Step 2: Override onCleared() and add logging
The ViewModel is destroyed when the associated fragment is detached, or when the activity is finished. Right before the ViewModel is destroyed, the onCleared() callback is called to clean up the resources.
- In the
GameViewModelclass, override theonCleared()method. - Add a log statement inside
onCleared()to track theGameViewModellifecycle.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}Step 3: Associate GameViewModel with the game fragment
A ViewModel needs to be associated with a UI controller. To associate the two, you create a reference to the ViewModel inside the UI controller.
In this step, you create a reference of the GameViewModel inside the corresponding UI controller, which is GameFragment.
- In the
GameFragmentclass, add a field of the typeGameViewModelat the top level as a class variable.
private lateinit var viewModel: GameViewModelStep 4: Initialize the ViewModel
During configuration changes such as screen rotations, UI controllers such as fragments are re-created. However, ViewModel instances survive. If you create the ViewModel instance using the ViewModel class, a new object is created every time the fragment is re-created. Instead, create the ViewModel instance using a ViewModelProvider.

How ViewModelProvider works:
ViewModelProviderreturns an existingViewModelif one exists, or it creates a new one if it does not already exist.ViewModelProvidercreates aViewModelinstance in association with the given scope (an activity or a fragment).- The created
ViewModelis retained as long as the scope is alive. For example, if the scope is a fragment, theViewModelis retained until the fragment is detached.
Initialize the ViewModel, using the ViewModelProviders.of() method to create a ViewModelProvider:
- In the
GameFragmentclass, initialize theviewModelvariable. Put this code insideonCreateView(), after the definition of the binding variable. Use theViewModelProviders.of()method, and pass in the associatedGameFragmentcontext and theGameViewModelclass. - Above the initialization of the
ViewModelobject, add a log statement to log theViewModelProviders.of()method call.
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)- Run the app. In Android Studio, open the Logcat pane and filter on
Game. Tap the Play button on your device or emulator. The game screen opens.
As shown in the Logcat, theonCreateView()method of theGameFragmentcalls theViewModelProviders.of()method to create theGameViewModel. The logging statements that you added to theGameFragmentand theGameViewModelshow up in the Logcat.

- Enable the auto-rotate setting on your device or emulator and change the screen orientation a few times. The
GameFragmentis destroyed and re-created each time, soViewModelProviders.of()is called each time. But theGameViewModelis created only once, and it is not re-created or destroyed for each call.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- Exit the game or navigate out of the game fragment. The
GameFragmentis destroyed. The associatedGameViewModelis also destroyed, and the callbackonCleared()is called.
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!
The ViewModel survives configuration changes, so it's a good place for data that needs to survive configuration changes:
- Put data to be displayed on the screen, and code to process that data, in the
ViewModel. - The
ViewModelshould never contain references to fragments, activities, or views, because activities, fragments, and views do not survive configuration changes.

For comparison, here's how the GameFragment UI data is handled in the starter app before you add ViewModel, and after you add ViewModel:
- Before you add
ViewModel:
When the app goes through a configuration change such as a screen rotation, the game fragment is destroyed and re-created. The data is lost. - After you add
ViewModeland move the game fragment's UI data into theViewModel:
All the data that the fragment needs to display is now theViewModel. When the app goes through a configuration change, theViewModelsurvives, and the data is retained.

In this task, you move the app's UI data into the GameViewModel class, along with the methods to process the data. You do this so the data is retained during configuration changes.
Step 1: Move data fields and data processing to the ViewModel
Move the following data fields and methods from the GameFragment to the GameViewModel:
- Move the
word,score, andwordListdata fields. Make surewordandscoreare notprivate.
Do not move the binding variable,GameFragmentBinding, because it contains references to the views. This variable is used to inflate the layout, set up the click listeners, and display the data on the screen—responsibilities of the fragment. - Move the
resetList()andnextWord()methods. These methods decide what word to show on the screen. - From inside the
onCreateView()method, move the method calls toresetList()andnextWord()to theinitblock of theGameViewModel.
These methods must be in theinitblock, because you should reset the word list when theViewModelis created, not every time the fragment is created. You can delete the log statement in theinitblock ofGameFragment.
The onSkip() and onCorrect() click handlers in the GameFragment contain code for processing the data and updating the UI. The code to update the UI should stay in the fragment, but the code for processing the data needs to be moved to the ViewModel.
For now, put the identical methods in both places:
- Copy the
onSkip()andonCorrect()methods from theGameFragmentto theGameViewModel. - In the
GameViewModel, make sure theonSkip()andonCorrect()methods are notprivate, because you will reference these methods from the fragment.
Here is the code for GameViewModel class, after refactoring:
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!")
}
}Here is the code for the GameFragment class, after refactoring:
/**
* 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()
}
}Step 2: Update references to click handlers and data fields in GameFragment
- In
GameFragment, update theonSkip()andonCorrect()methods. Remove the code to update the score and instead call the correspondingonSkip()andonCorrect()methods onviewModel. - Because you moved the
nextWord()method to theViewModel, the game fragment can no longer access it.
InGameFragment, in theonSkip()andonCorrect()methods, replace the call tonextWord()withupdateScoreText()andupdateWordText(). These methods display the data on the screen.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}- In the
GameFragment, update thescoreandwordvariables to use theGameViewModelvariables, because these variables are now in theGameViewModel.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}- In the
GameViewModel, inside thenextWord()method, remove the calls to theupdateWordText()andupdateScoreText()methods. These methods are now being called from theGameFragment. - Build the app and make sure there are no errors. If you have errors, clean and rebuild the project.
- Run the app and play the game through some words. While you are in the game screen, rotate the device. Notice that the current score and the current word are retained after the orientation change.
Great job! Now all your app's data is stored in a ViewModel, so it is retained during configuration changes.
In this task, you implement the click listener for the End Game button.
- In
GameFragment, add a method calledonEndGame(). TheonEndGame()method will be called when the user taps the End Game button.
private fun onEndGame() {
}- In
GameFragment, inside theonCreateView()method, locate the code that sets click listeners for the Got It and Skip buttons. Just beneath these two lines, set a click listener for the End Game button. Use the binding variable,binding. Inside the click listener, call theonEndGame()method.
binding.endGameButton.setOnClickListener { onEndGame() }- In
GameFragment, add a method calledgameFinished()to navigate the app to the score screen. Pass in the score as an argument, using 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)
}- In the
onEndGame()method, call thegameFinished()method.
private fun onEndGame() {
gameFinished()
}- Run the app, play the game, and cycle through some words. Tap the End Game button. Notice that the app navigates to the score screen, but the final score is not displayed. You fix this in the next task.
|
|
When the user ends the game, the ScoreFragment does not show the score. You want a ViewModel to hold the score to be displayed by the ScoreFragment. You'll pass in the score value during the ViewModel initialization using the factory method pattern.
The factory method pattern is a creational design pattern that uses factory methods to create objects. A factory method is a method that returns an instance of the same class.
In this task, you create a ViewModel with a parameterized constructor for the score fragment and a factory method to instantiate the ViewModel.
- Under the
scorepackage, create a new Kotlin class calledScoreViewModel. This class will be theViewModelfor the score fragment. - Extend the
ScoreViewModelclass fromViewModel.Add a constructor parameter for the final score. Add aninitblock with a log statement. - In the
ScoreViewModelclass, add a variable calledscoreto save the final score.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}- Under the
scorepackage, create another Kotlin class calledScoreViewModelFactory. This class will be responsible for instantiating theScoreViewModelobject. - Extend the
ScoreViewModelFactoryclass fromViewModelProvider.Factory. Add a constructor parameter for the final score.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}- In
ScoreViewModelFactory, Android Studio shows an error about an unimplemented abstract member. To resolve the error, override thecreate()method. In thecreate()method, return the newly constructedScoreViewModelobject.
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")
}- In
ScoreFragment, create class variables forScoreViewModelandScoreViewModelFactory.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory- In
ScoreFragment, insideonCreateView(), after initializing thebindingvariable, initialize theviewModelFactory. Use theScoreViewModelFactory. Pass in the final score from the argument bundle, as a constructor parameter to theScoreViewModelFactory().
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)- In
onCreateView(), after initializingviewModelFactory, initialize theviewModelobject. Call theViewModelProviders.of()method, pass in the associated score fragment context andviewModelFactory. This will create theScoreViewModelobject using the factory method defined in theviewModelFactoryclass.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)- In
onCreateView()method, after initializing theviewModel, set the text of thescoreTextview to the final score defined in theScoreViewModel.
binding.scoreText.text = viewModel.score.toString()- Run your app and play the game. Cycle through some or all the words and tap End Game. Notice that the score fragment now displays the final score.

- Optional: Check the
ScoreViewModellogs in the Logcat by filtering onScoreViewModel. The score value should be displayed.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
In this task, you implemented ScoreFragment to use ViewModel. You also learned how to create a parameterized constructor for a ViewModel using the ViewModelFactory interface.
Congratulations! You changed the architecture of your app to use one of the Android Architecture Components, ViewModel. You resolved the app's lifecycle issue, and now the game's data survives configuration changes. You also learned how to create a parameterized constructor for creating a ViewModel, using the ViewModelFactory interface.
Android Studio project: GuessTheWord
- The Android app architecture guidelines recommend separating classes that have different responsibilities.
- A UI controller is UI-based class like
ActivityorFragment. UI controllers should only contain logic that handles UI and operating system interactions; they shouldn't contain data to be displayed in the UI. Put that data in aViewModel. - The
ViewModelclass stores and manages UI-related data. TheViewModelclass allows data to survive configuration changes such as screen rotations. ViewModelis one of the recommended Android Architecture Components.ViewModelProvider.Factoryis an interface you can use to create aViewModelobject.
The table below compares UI controllers with the ViewModel instances that hold data for them:
UI controller | ViewModel |
An example of a UI controller is the | An example of a |
Doesn't contain any data to be displayed in the UI. | Contains data that the UI controller displays in the UI. |
Contains code for displaying data, and user-event code such as click listeners. | Contains code for data processing. |
Destroyed and re-created during every configuration change. | Destroyed only when the associated UI controller goes away permanently—for an activity, when the activity finishes, or for a fragment, when the fragment is detached. |
Contains views. | Should never contain references to activities, fragments, or views, because they don't survive configuration changes, but the |
Contains a reference to the associated | Doesn't contain any reference to the associated UI controller. |
Udacity course:
Android developer documentation:
- ViewModel Overview
- Handling Lifecycles with Lifecycle-Aware Components
- Guide to app architecture
ViewModelProviderViewModelProvider.Factory
Other:
- MVVM (model-view-viewmodel) architectural pattern.
- Separation of concerns (SoC) design principle
- Factory method pattern
This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:
- Assign homework if required.
- Communicate to students how to submit homework assignments.
- Grade the homework assignments.
Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.
If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.
Answer these questions
Question 1
To avoid losing data during a device-configuration change, you should save app data in which class?
ViewModelLiveDataFragmentActivity
Question 2
A ViewModel should never contain any references to fragments, activities, or views. True or false?
- True
- False
Question 3
When is a ViewModel destroyed?
- When the associated UI controller is destroyed and recreated during a device-orientation change.
- In an orientation change.
- When the associated UI controller is finished (if it's an activity) or detached (if it's a fragment).
- When the user presses the Back button.
Question 4
What is the ViewModelFactory interface for?
- Instantiating a
ViewModelobject. - Retaining data during orientation changes.
- Refreshing the data being displayed on the screen.
- Receiving notifications when the app data is changed.
Start the next lesson:
For links to other codelabs in this course, see the Android Kotlin Fundamentals codelabs landing page.




