nevermore
nevermore

Reputation: 41

best way for injecting ViewModels?

I have used koin in the past and injecting viewModel with koin is a single liner. I need to know how to do that without it! should We use a big switch/case in ViewModelFactory for different viewmodels?

class ViewModelFactory: ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       return when(modelClass) {
            is FirstViewModel -> FirstViewModel()
            is SecondViewModel -> SecondViewModel()
                ...
        }
    }
}

but then I have to inject all the dependencies of all viewmodels to the factory. and that's really messy code. even without that the switch/case by itself is messy! I don't think you should do that specially in big projects. so what are the alternative ways of doing this?
how can dagger help with this?

Upvotes: 3

Views: 7601

Answers (1)

Mohsen
Mohsen

Reputation: 1389

there are actually quite good alternatives to that.

first: the first one which people tend to forget about is having a ViewModelfactory per ViewModel it's a lot better than having a massive factory. that is what we call the Single Responsibility Principle

class FirstViewModelFactory(val firstDependency: SomeClass, val secondDependency: SomeOtherClass): ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        FirsViewModel(firstDependency, secondDependency) as T
    }
}

and in the activity:

val viewModel: FirstViewModel = ViewModelProvider(this, FirstViewModelFactory(first, second))[FirstViewModel::class.java]

Second: if you want to have only one Factory for all your ViewModels you can define a generic factory with that takes a lambda:

class ViewModelFactory<VM: ViewModel>(val provider: () -> VM): ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return  provider() as T
    }
}

and use it like this in the activity or fragment:

val ViewModel: FirstViewModel = ViewModelProvider(this, ViewModelFactory<FirstViewModel>{
    FirstViewModel(first, second)
})[FirstViewModel::class.java]

this actually makes your life a lot easier. but it can still be improved

Third: you can tell dagger how to provide you FirstViewModel and its dependencies. if you don't know how to use dagger, you try to learn it then read this part. you need to tell dagger that you want to get an instance of FirstViewModle. the place for that is in the AppComponent.

@Component(modules = [AppModule::class])
interface AppComponent {
    val applicationContext: Context
    val firstViewModel: FirstViewModel
    
    ...

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance applicationContext: Context): AppComponent
    }
}

and in your appModule, you need to tell dagger how to provide dependencies of FirstViewModel with @Provides annotation. then you need to instantiate your dagger component in application class, there are lots of ways to do this, I just use the factory interface:

class MyApplication: Application() {
    val component: AppComponent by lazy {
        DaggerAppComponent.factory().create(applicationContext)
    }
}

don't forget to add MyApplication in the manifest.

then in your activity, you can inject the viewModel without ever being worry about the dependencies:

val appComponent = (application as MyApplication).appComponent

val viewModel: FirstViewModel = ViewModelProvider(this, ViewModelFactory<FirstViewModel>{
    appComponent.firstViewModel
})[FirstViewModel::class.java]

you can make it more beautiful and readable using lazy and extension functions and you can eventually have something like this:

private val viewModel: FirstViewModel by injectVmWith { appInjector.firstViewModel }

Fourth: you can always use dagger's multibinding. with this, you inject ViewModelFactory to activity or fragment and you retrieve the viewModel from that. with this, you are telling dagger to put all your viewModels in a map and inject it to the ViewModelFactory. you help dagger to find viewModels by annotating them. and you also tell dagger what their key is using another annotation. you do all that in another module and for every viewmodel you need a function in your viewModel module. then instead of switch/case in your massive factory, you tell dagger to get the required viewmodel from the map based on its type. it's a service locator (anti?)pattern. it's another topic by itself and this answer is already too long. refer to this or this

Summary : I think if you are using dagger the third one is definitely the winner. multibinding is also good but every time you add a viewmodel you need to remember to add a function in the viewmodelModule too(just like Koin). and if you are not using dagger, the second way in my opinion is the best but you should decide what is best for you.
Koin is also great!

Upvotes: 4

Related Questions