Search code examples
androiddagger-2android-viewmodel

Dagger2 inject specific instance of abstract ViewModel


I have a ViewModelFactory implemented as follows:

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

This works fine with my current ViewModel:

class MainActivityViewModel @Inject constructor(private val dependency: Dependency) : ViewModel()
//... in the activity:
@Inject
lateinit var factory: ViewModelFactory<MainActivityViewModel>

private val viewModel: MainActivityViewModel by viewModels { factory }

However I have a different build flavour that I want to implement where the behaviour is different, so I have created an AbstractViewModel:

abstract class AbstractViewModel : ViewModel()
//...and so now
class MainActivityViewModel @Inject constructor(private val dependency: Dependency) : AbstractViewModel()
//... and in the activity
@Inject
lateinit var factory: ViewModelFactory<AbstractViewModel>

private val viewModel: AbstractViewModel by viewModels { factory }

I want to be able to provide the specific instance to the ViewModelFactory, but I am not sure how to achieve this.


Solution

  • Solved it. Answering for future reference and anyone who may be interested in doing something similar.

    Step 1: Add a new module component

        @Component(
            modules = [
                //...
                ViewModelModule::class
            ]
        )
        interface ApplicationComponent { //...
    

    This allows developers to create a FlavourApplicationComponent that can have an alternate to ViewModelModule, which I called MockViewModelModule

    Step 2: Define the modules

        //in the main flavour
        @Module
        class ViewModelModule {
            @Provides
            fun provideMainActivityViewModel(mainActivityViewModel: MainActivityViewModel): AbstractViewModel
            = mainActivityViewModel
        }
    
        //in the mock flavour
        @Module
        class MockViewModelModule {
            @Provides
            fun provideMainActivityViewModel(mainActivityViewModel: MainActivityViewModel): AbstractViewModel
            = MockMainActivityViewModel()
        }
    

    And this can actually be configured at run time, and therefore you can allow users to test all the different states of your app without them needing to GET into those states.