ViewModel overview (Views)
Concepts and Jetpack Compose implementation
The ViewModel class is a business logic or screen level state
holder. It exposes state to the UI and encapsulates related business logic.
Its principal advantage is that it caches state and persists it through
configuration changes. This means that your UI doesn't have to fetch data again
when navigating between activities, or following configuration changes, such as
when rotating the screen.
ViewModel benefits
The alternative to a ViewModel is a plain class that holds the data you display in your UI. This can become a problem when navigating between activities or Navigation destinations. Doing so destroys that data if you don't store it using the saved instance state mechanism. ViewModel provides a convenient API for data persistence that resolves this issue.
The key benefits of the ViewModel class are essentially two:
- It lets you persist UI state.
- It provides access to business logic.
Scope
When you instantiate a ViewModel, you pass it an object that implements the
ViewModelStoreOwner interface. This may be a Navigation destination,
Navigation graph, activity, fragment, or any other type that implements the
interface. Your ViewModel is then scoped to the Lifecycle of the
ViewModelStoreOwner. It remains in memory until its ViewModelStoreOwner
goes away permanently.
A range of classes are either direct or indirect subclasses of the
ViewModelStoreOwner interface. The direct subclasses are
ComponentActivity, Fragment, and NavBackStackEntry.
For a full list of indirect subclasses, see the
ViewModelStoreOwner reference.
Implement a ViewModel
The following is an example implementation of a ViewModel for a screen that allows the user to roll dice.
Kotlin
dataclassDiceUiState(
valfirstDieValue:Int?=null,
valsecondDieValue:Int?=null,
valnumberOfRolls:Int=0,
)
classDiceRollViewModel:ViewModel(){
// Expose screen UI state
privateval_uiState=MutableStateFlow(DiceUiState())
valuiState:StateFlow<DiceUiState>=_uiState.asStateFlow()
// Handle business logic
funrollDice(){
_uiState.update{currentState->
currentState.copy(
firstDieValue=Random.nextInt(from=1,until=7),
secondDieValue=Random.nextInt(from=1,until=7),
numberOfRolls=currentState.numberOfRolls+1,
)
}
}
}
Java
publicclass DiceUiState{
privatefinalIntegerfirstDieValue;
privatefinalIntegersecondDieValue;
privatefinalintnumberOfRolls;
// ...
}
publicclass DiceRollViewModelextendsViewModel{
privatefinalMutableLiveData<DiceUiState>uiState=
newMutableLiveData(newDiceUiState(null,null,0));
publicLiveData<DiceUiState>getUiState(){
returnuiState;
}
publicvoidrollDice(){
Randomrandom=newRandom();
uiState.setValue(
newDiceUiState(
random.nextInt(7)+1,
random.nextInt(7)+1,
uiState.getValue().getNumberOfRolls()+1
)
);
}
}
You can then access the ViewModel from an activity as follows:
Kotlin
importandroidx.activity.viewModels
classDiceRollActivity:AppCompatActivity(){
overridefunonCreate(savedInstanceState:Bundle?){
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same DiceRollViewModel instance created by the first activity.
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
valviewModel:DiceRollViewModelbyviewModels()
lifecycleScope.launch{
repeatOnLifecycle(Lifecycle.State.STARTED){
viewModel.uiState.collect{
// Update UI elements
}
}
}
}
}
Java
publicclass MyActivityextendsAppCompatActivity{
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
DiceRollViewModelmodel=newViewModelProvider(this).get(DiceRollViewModel.class);
model.getUiState().observe(this,uiState->{
// update UI
});
}
}
Recommended for you
- Note: link text is displayed when JavaScript is off
- Use Kotlin coroutines with lifecycle-aware components
- Save UI states
- Load and display paged data