I am new to Dagger 2 in android. I am having trouble understanding how to inject ViewModel
with dynamic value. So Far I have successfully injected ViewModel using dagger multi binding with pre-defined repository dependency. Here's my code.
ApplicationComponent
@Singleton
@Component(modules = [AppModule::class, SubComponentsModule::class, ViewModelFactoryModule::class])
interface ApplicationComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance applicationContext: Context): ApplicationComponent
}
fun activityComponent(): ActivitySubComponent.Factory
fun fragmentComponent(): FragmentSubComponent.Factory
}
FragmentModule
@Module
abstract class FragmentModule {
@Binds
@IntoMap
@ViewModelKey(WeatherViewModel::class)
abstract fun bindWeatherView(weatherViewModel: WeatherViewModel) : ViewModel
}
ViewModelFactoryModule
@Module
class ViewModelFactoryModule {
@Provides
@Singleton
fun viewModelFactory(providerMap: Map<Class<out ViewModel>, Provider<ViewModel>>): ViewModelProvider.Factory {
return ViewModelFactory(providerMap)
}
}
Application class
class ThisApplication: Application(),InjectorProvider {
override fun onCreate() {
super.onCreate()
Stetho.initializeWithDefaults(this)
}
override val component by lazy {
DaggerApplicationComponent.factory().create(applicationContext)
}
}
I'm using InjectorProvider
interface to get dagger to fragments and activity without having to cast every time.
InjectorProvider
interface InjectorProvider {
val component: ApplicationComponent
}
val Activity.injector get() = (application as InjectorProvider).component
val Fragment.injector get() = (requireActivity().application as InjectorProvider).component
This is the simple ViewModel
I used for testing ViewModel
injection.
WeatherViewModel
class WeatherViewModel @Inject constructor(val repository: WeatherRepository): ViewModel() {
fun printMessage(){
Log.d("WeatherViewModel","ViewModel binding is working")
repository.printMessage()
}
}
Finally, I Injected this view model into a fragment like below.
WeatherFragment
class WeatherFragment : Fragment() {
@Inject
lateinit var viewModelFactory: ViewModelFactory
override fun onAttach(context: Context) {
injector.fragmentComponent().create().injectWeatherFragment(this)
super.onAttach(context)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val mainActivityViewModel =
ViewModelProvider(this,viewModelFactory)[WeatherViewModel::class.java]
mainActivityViewModel.printMessage()
}
}
This part is working fine. The Log message inside printMessage()
getting printed. I saw in the dagger issue discussion that using AssistedInject is the best approach to handle this kind of scenario. I changed my ViewModle
by adding a simple int value as a parameter.
Edited WeatherViewModel
class WeatherViewModel @AssistedInject constructor(val repository: WeatherRepository,
@Assisted val id: Int): ViewModel() {
@AssistedInject.Factory
interface Factory{ fun create(id: Int) : WeatherViewModel }
fun printMessage(){
Log.d("WeatherViewModel","ViewModel binding is working")
repository.printMessage()
}
}
Edited ApplicationComponent
@Singleton
@Component(modules = [AppModule::class, SubComponentsModule::class, ViewModelFactoryModule::class, AssistedInjectModule::class])
interface ApplicationComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance applicationContext: Context): ApplicationComponent
}
fun activityComponent(): ActivitySubComponent.Factory
fun fragmentComponent(): FragmentSubComponent.Factory
}
@AssistedModule
@Module(includes = [AssistedInject_AssistedInjectModule::class])
interface AssistedInjectModule
From this point onwards I don't understand how to inject ViewModel
into fragment with repository plus dynamic "id" value. If I inject WeatherViewModel.Factory
into the fragment by calling the create method (val mainActivityViewModel = factory.create(5)
) it won't fulfill the repository dependency in ViewModel
. How to combine these two solutions to have pre-defined repository dependency with dynamic value? OR is there any other better way of approaching this?