1. Introduction
The ViewModel class is a kind of business logic or screen-level state container. It is used to expose state to interfaces, and to encapsulate related business logic. Its main advantage is that it caches state and persists it across configuration changes. This means that the interface will not need to refetch data when navigating between activities or after making configuration changes, such as when the screen is rotated.
Advantages of ViewModels
An alternative to ViewModel is a normal class that holds the data to be displayed in the interface. This can cause problems when navigating between activities or Navigation destinations. At this point, if you do not store the corresponding data using the save instance state mechanism, the system will destroy the corresponding data. ViewModel provides a convenient data persistence API that can solve this problem.
The main advantage of the ViewModel class is actually twofold:
- It allows you to persist UI state.
- It can provide access to business logic.
Persistence
The ViewModel allows data to survive the state held by the ViewModel and the actions triggered by the ViewModel. This caching means that you don't need to re-fetch data after common configuration changes, such as screen rotations, are made.
scope
When you instantiate a ViewModel, you pass it an object that implements the ViewModelStoreOwner interface. It could be a Navigation Destination, Navigation Chart, Activity, Fragment, or any other type that implements the interface. The ViewModel will then be scoped to ViewModelStoreOwner
the Lifecycle . It remains in memory until it is ViewModelStoreOwner
permanently gone.
There are a series of classes that are ViewModelStoreOwner
direct or indirect subclasses of an interface. Direct subclasses are ComponentActivity , Fragment , and NavBackStackEntry .
When the ViewModel's scoped fragment or activity is destroyed, asynchronous work continues in the ViewModel scoped to that fragment or activity. This is the key to persistence.
Implement ViewModel
ViewModel is a class. When we use it, we only need to inherit it, but viewModel and UI data are updated through LiveData, which is the core of the so-called MVVM.
class DiceRollViewModel : ViewModel() {
private var data: MutableLiveData<String>?=null
fun getData(): MutableLiveData<String> {
if (data == null) {
data = MutableLiveData()
}
return data!!
}
fun updateValue(){
val value=kotlin.random.Random.nextInt(1,100);
data?.value="随机生成数为${value}"
}
}
ViewModel creation
The core of using viewModel is to manage the life cycle through viewModel, so when creating it, it is not through direct new, but there is a special tool ViewModelProvider
ViewModelProvider: use
The two constructs are as follows:
-
public constructor(owner: ViewModelStoreOwner)
-
public constructor(owner: ViewModelStoreOwner, factory: Factory)
Because ViewModelStoreOwner has been processed inside fragment and Activity, it can be used directly with the help of ViewModelProvider
Normal use:
viewModel = ViewModelProvider(this).get(DiceRollViewModel::class.java)
If you need factory creation, you can use the factory from method
companion object { @JvmStatic fun from(vararg initializers: ViewModelInitializer<*>): Factory = InitializerViewModelFactory(*initializers) }
Small scale chopper:
class TestViewModelActivity : BaseActivity() {
private lateinit var bind: MyVideModelBind
private lateinit var viewModel: DiceRollViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bind = DataBindingUtil.setContentView(this, R.layout.layout_viewmodel_demo)
viewModel = ViewModelProvider(this).get(DiceRollViewModel::class.java)
viewModel.getData().observe(this, Observer {
bind.value = it
})
bind.btnUpdate.setOnClickListener {
viewModel.updateValue()
}
}
}
Note :
ViewModels should generally not have references to views, lifecycles , or any classes that might store references to the activity context. Since the ViewModel's lifecycle is greater than that of the interface, keeping lifecycle-related APIs in the ViewModel can lead to memory leaks.
ViewModel life cycle
Following dependency injection best practices, ViewModels can take dependencies as parameters in their constructors. This is mostly the type in the domain layer or data layer. Since the framework provides the ViewModel, a special mechanism is required to create an instance of the ViewModel. The mechanism is the ViewModelProvider.Factory interface. Only implementations of this interface can instantiate ViewModels within the appropriate scope.
ViewModel containing CreationExtras
If the ViewModel class receives dependencies in its constructor, provide a factory that implements the ViewModelProvider.Factory interface. Replace create(Class<T>, CreationExtras) function to provide new instance of ViewModel
With CreationExtras, you can access relevant information that helps instantiate the ViewModel. The keys that can be accessed via extra are listed below:
key | Function |
---|---|
ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY | Provides ViewModelProvider.get() access to the custom key you pass to . |
ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY | Provides Application access to class instances. |
SavedStateHandleSupport.DEFAULT_ARGS_KEY | Provides SavedStateHandle access to the parameter bundle that you should use when constructing. |
SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY | Provides access ViewModel to the .SavedStateRegistryOwner |
SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY | Provides access ViewModel to the .ViewModelStoreOwner |
To create a new instance of SavedStateHandle, use the CreationExtras.createSavedStateHandle().createSavedStateHandle()) function and pass it to the ViewModel.
ViewModel Scope API
Scope is the key to using ViewModel effectively. Each ViewModel is scoped to an object that implements the ViewModelStoreOwner interface. There are several APIs that help you manage the scope of ViewModels more easily. This document briefly introduces some key technologies that you should know about
The ViewModel is scoped to the nearest ViewModelStoreOwner
Which fragment or activity theme can be specified
val viewModel: MyiewModel by viewModels( ownerProducer = { requireParentFragment() })
ViewModel is scoped to Navigation
About how to use Navigation, you can read one of my articles:
Introduction and use of the Navigation component of Android navigation
The Navigation graph is also the ViewModel Store Owner. If you are using Navigation Fragment or Navigation Compose, you can use navGraphViewModels(graphId)
val viewModel: MyViewModel by viewModels(
{ findNavController().getBackStackEntry(R.id.nav_graph) }
)
Source code analysis:
After binding Navigation, it already supports storage, ViewModelStore
private final HashMap<UUID, ViewModelStore> mViewModelStores = new HashMap<>();
and state save saveStateHandle
The life cycle of Navigation can also synchronize the life cycle of the host.