Search code examples
mvvmdagger-2daggerandroid-viewmodelandroid-mvvm

Do we really need viewModelFactories and viewmodelProviders when using Dagger?


So I was working on some sample MVVM project using Dagger. I have a viewmodel factory that goes like this:

class DaggerViewModelFactory @Inject constructor(private val viewModelsMap: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val creator = viewModelsMap[modelClass] ?:
        viewModelsMap.asIterable().firstOrNull {
            modelClass.isAssignableFrom(it.key)
        }?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
        return try {
            creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

A viewmodel factory module

@Module
abstract class ViewModelFactoryModule {
    @Binds
    abstract fun bindViewModelFactory(viewModelFactory: DaggerViewModelFactory): ViewModelProvider.Factory
}

I got a ViewModelModule:

@Module
abstract class MyViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(TakePicturesViewModel::class)
    abstract fun bindTakePictureViewModel(takePicturesViewModel: TakePicturesViewModel): ViewModel
}

A component that goes like this:

@PerActivity
@Subcomponent(modules = [ActivityModule::class, ViewModelFactoryModule::class, MyViewModelModule::class])
interface ActivityComponent {
    fun inject(mainActivity: MainActivity)
}

An a viewmodel that goes like this:

class TakePicturesViewModel @Inject constructor(app: Application): AndroidViewModel(app) {...

So I can either inject my viewmodel in my activity using a view model factory like this:

    @Inject
    lateinit var viewModelFactory: DaggerViewModelFactory
private lateinit var takePicturesViewModel: TakePicturesViewModel
.
.
.
    takePicturesViewModel = ViewModelProviders.of(this, viewModelFactory).get(TakePicturesViewModel::class.java)

Or with not viewmodel factory at all, like this:

@Inject
lateinit var takePicturesViewModel: TakePicturesViewModel

Both ways work, so I was wondering which one is the right way to work, if using Dagger allows me to inject a viewmodel without needing a viewmodelfactory, is there a good reason to keep it?, or should I just get rid of this viewmodelfactory?

Thanks in advance for any advice.

Greetings


Solution

  • Both ways work, so I was wondering which one is the right way to work, if using Dagger allows me to inject a viewmodel without needing a viewmodelfactory, is there a good reason to keep it?, or should I just get rid of this viewmodelfactory?

    Both ways work differently. Try rotating your screen with stored data in your ViewModel and you'll see.

    Dagger can create the ViewModel, which is what you make use of in that generic ViewModelFactory. Those view models should be unscoped, thus you'll be creating a new ViewModel every single time. The Android support library will cache that ViewModel and reuse it after rotation so that you can keep your data—the factory method gets called once and there will only ever be one ViewModel created (per lifecycle). You keep your data and everything behaves as expected.

    If on the other hand you use Dagger to inject your ViewModel directly none of the above will apply. Like any other dependency, a new ViewModel will be injected on creation, leading to a ViewModel being created every single time it is used—you'll not only use the data stored in it, you won't be able to share state with fragments either.

    Of course you could apply a scope to the ViewModel, but that scope should be longer lived than the Activity instance (to keep state between rotations), but no longer lived than the screen is visible. So you can neither scope it to the activity nor to the application lifecycle. You can get it to work by introducing a new scope in-between, but at this point you'd be reinventing the ViewModel library.


    tl;dr Inject and use the factory or you'll get a confusing/wrong ViewModel implementation.