The creation of the ViewModel
ViewModel itself is just a subclass of ViewModel:
class MainViewModel: ViewModel() {}Copy the code
How does it have the ability to hold data while the screen rotates and the UI is rebuilt? When was it cleaned up? The answer has everything to do with how it’s created and preserved.
This article reviews several common ways to create a ViewModel. Note: The diagrams in this article are not strictly sequence diagrams (nor do they conform to the specification), but are intended to sketch the invocation relationships in the code.
Manually create the ViewModel natively
When the ViewModel has no construction parameters
It’s easy when the ViewModel has no arguments:
class MainViewModel: ViewModel() {}
class MainActivity : ComponentActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
}
}
Copy the code
It’s not just new, it’s got to get it through the ViewModelProvider.
Note: This cannot be called before the Activity’s onCreate(), which means you cannot declare fields to assign directly.
Otherwise you will get this error:
Caused by: java.lang.IllegalStateException: Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.
Copy the code
The method call looks something like this:
From here we can see that the ViewModelProvider tool provides viewModels with two other things:
ViewModelStore
: Is responsible for storing ViewModel.Factory
: is responsible for instantiating the specific ViewModel type.
Please keep these two points in mind.
In the simplest example above:
- The Activity is
ViewModelStoreOwner
It can begetViewModelStore()
When the ViewModelStoreOwner(such as the Activity) is rebuilt because of configuration changes, the new owner will still get the old ViewModelStore instance.
- We didn’t pass
Factory
, so we end up with no parametersNewInstanceFactory
.
When the ViewModel has construction parameters
But usually, the ViewModel will need some dependencies, and we’ll need to pass in some parameters from the construct
class MainViewModel(
private val repository: MainRepository,
) : ViewModel()
Copy the code
At this point our factory needs to implement itself: we need to pass the dependent object to the factory so that it can use it when constructing the ViewModel:
class MyViewModelFactory constructor(private val repository: MainRepository) :
ViewModelProvider.Factory {
override fun
create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")}}Copy the code
Then pass our factory to the ViewModelProvider:
class MainActivity : ComponentActivity() {
private lateinit var viewModel: MainViewModel
private val repository: MainRepository = MainRepository()
private val viewModelFactory: MyViewModelFactory = MyViewModelFactory(repository)
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this, viewModelFactory).get(MainViewModel::class.java)
}
}
Copy the code
The process would look something like this:
With the help of AndroidX
Thanks to the By viewModels() property agent in the AndroidXActivity-ktx package, our code above can be simplified to look like this:
class MyViewModelFactory constructor(private val repository: MainRepository) :
ViewModelProvider.Factory {
override fun
create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")}}class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels {
viewModelFactory
}
private val repository: MainRepository = MainRepository()
private val viewModelFactory: MyViewModelFactory = MyViewModelFactory(repository)
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
// use viewModel}}Copy the code
The factory still writes it itself, simplifying the provider get part:
- Extension method that implies an activity object.
- Lazy circumvents the lifecycle issue as long as the ViewModel is not used
onCreate
Just before.
The ViewModel is simpler with no arguments:
private val viewModel: MainViewModel by viewModels()
Copy the code
Dagger era
With dagger, we can tag @inject on the construct to tell dagger to help us create a repository and viewModelFactory:
class MainRepository @Inject constructor() {}@Singleton
class MyViewModelFactory @Inject constructor(private val repository: MainRepository) :
ViewModelProvider.Factory {
override fun
create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")}}Copy the code
But we can’t do this with the ViewModel, because the ViewModel should be stored in the ViewModelStore, which is provided by the Activity.
We’ll inject the viewModelFactory and use it to create the ViewModel:
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels {
viewModelFactory
}
@Inject
lateinit var viewModelFactory: MyViewModelFactory
}
Copy the code
Because you have a dagger, you’ll need to write something like this inside onCreate() :
(applicationContext as MyApplication).appComponent.inject(this)
Copy the code
The DI framework simplifies the construction of dependencies and factories.
Hilt era
Mark the ViewModel with @hiltViewModel, construct the tag with @inject:
@HiltViewModel
class MainViewModel @Inject constructor(
private val repository: MainRepository,
) : ViewModel() {
}
Copy the code
Dependencies can also construct the tag @inject:
class MainRepository @Inject constructor() {}Copy the code
Add the comment @androidentryPoint to the Activity and also use by viewModels():
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
// use viewModel}}Copy the code
Note that unlike the dagger, there is no injection factory required (nor write factory required), Hilt uses its own factory, HiltViewModelFactory.
This process:
Compose’s ViewModel() method
Get in Compose a ViewModel is very simple, you can use only one ViewModel () method. (this method in androidx. Lifecycle: lifecycle – ViewModel – Compose depend on the bag).
@Composable
fun Greeting(name: String) {
val viewModel: MainViewModel = viewModel()
// use viewModel
}
Copy the code
SetContent is Compose in the Activity’s onCreate (), so here will set all kinds of market metrix, related to the ViewModel is the LocalViewModelStoreOwner, And then the contents of the package can get the owner at any time, get the ViewModelStore and ViewModel.
Compose’s hiltViewModel() method
The viewModel() method above binds the scope of the viewModel to the current Activity or Fragment, no matter where it is retrieved. What if we had multiple composable interfaces?
The composable method hiltViewModel() obtains a scope for a navigation destination. (This method is contained in the Androidx.hilt: Hilt-navigation-compose dependency).
NavHost(navController = navController, startDestination = "friendslist") {
composable("friendslist") {
val viewModel = hiltViewModel<MainViewModel>()
FriendsList(viewModel = viewModel, navHostController = navController)
}
...
}
Copy the code
The ViewModel acquired in this way has a lifetime associated with the navigation destination. When you exit the interface, the ViewModel is cleared and the next entry is a new object.
The owner is no longer an Activity or Fragment, but a NavBackStackEntry. I’m not going to talk about navigation.
conclusion
The creation of the ViewModel is critical to its life cycle. It’s a lot of boilerplate code.
This article summarizes several common approaches to creation, and hopefully leaves the reader with a clearer understanding of how each approach works and what the convenient tools do for us.