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 isViewModelStoreOwnerIt 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 passFactory, 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 usedonCreateJust 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.