Reputation: 41
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
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