In one of my previous posts I have described how to implement a ViewModel factory that was able to provide ViewModels with their dependencies injected, e.g. an API client, and it was good enough for me at that time. Later on, thanks to Piotr, we've found out even better and simpler approach with an additional possibility of injecting Activity- or Fragment-dependant data into ViewModels.

{: .center-image}

Simpler factory

Previously, we've created a singleton factory that was supplied with a map of ViewModel -based classes and their respective Provider s. It required us to create a custom ViewModelKey annotation and use Dagger to generate the map using IntoMap bindings. It didn't require a lot of boilerplate code compared to some other solutions I saw at that time, but it wasn't perfect either.

On the contrary, the new solution is based on a generic ViewModel factory class of which instances are created for each Activity or Fragment instance.

import android.arch.lifecycle.ViewModel import android.arch.lifecycle.ViewModelProvider import dagger.Lazy import javax.inject.Inject class ViewModelFactory<VM : ViewModel> @Inject constructor( private val viewModel: Lazy<VM> ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return viewModel.get() as T } }

For example (see the full code here):

class MainViewModel @Inject constructor( private val apiClient: ApiClient ) : ViewModel() { // ... } class MainActivity : BaseActivity() { @Inject lateinit var vmFactory: ViewModelFactory<MainViewModel> lateinit var vm: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) vm = ViewModelProviders.of(this, vmFactory)[MainViewModel::class.java] // ... } }

As you can see, there is much less code and personally I think it's also easier to understand. To make it even more concise, we can add an extension function in the BaseActivity class like this:

abstract class BaseActivity : AppCompatActivity() { // ... inline fun <reified T : ViewModel> ViewModelFactory<T>.get(): T = ViewModelProviders.of(this@BaseActivity, this)[T::class.java] }

Then, we can get a ViewModel with just: vm = vmFactory.get()

Analogically, we can add a similar function for Fragments.

More possibilities

One of the issues we've had was that the singleton factory holding a map of ViewModel providers was widely scoped, therefore it wouldn't let us inject anything coming from a more narrow scope, e.g. Activity's extras or Fragment's arguments.

Creating a new factory each time makes it possible. In order to achieve this, we need an additional module that knows how to obtain the dependencies. For example:

import com.azabost.simplemvvm.net.response.RepoResponse import dagger.Module import dagger.Provides @Module class RepoActivityIntentModule { @Provides fun providesRepoResponse(activity: RepoActivity): RepoResponse { return activity.intent.getSerializableExtra(RepoActivity.REPO_RESPONSE_EXTRA) as RepoResponse } }

This module must then be added to the respective RepoActivity subcomponent generated by the ContributesAndroidInjector annotation:

import com.azabost.simplemvvm.ui.main.MainActivity import com.azabost.simplemvvm.ui.repo.RepoActivity import com.azabost.simplemvvm.ui.repo.RepoActivityIntentModule import dagger.Module import dagger.android.ContributesAndroidInjector @Module abstract class AndroidInjectorsModule { @ContributesAndroidInjector abstract fun contributeMainActivity(): MainActivity @ContributesAndroidInjector(modules = [RepoActivityIntentModule::class]) abstract fun contributeRepoActivity(): RepoActivity }

Finally, when we get our RepoViewModel in the RepoActivity , it has the data coming from the intent already injected: