Reputation: 4731
I have gone through all the answers with the above title but couldn't find the solution. Basically I want to do scoping. I want to inject ApiService only to HomeViewModel
. It should not be available to LoginViewModel
. I have my following setup and the error which I am getting:
Info: If I remove the provideLoginActivity()
from ActivityModule
everything works fine. Why behaving like that?
AppComponent:
@Singleton
@Component(
modules = [AndroidInjectionModule::class, ActivityModule::class, AppModule::class]
)
interface AppComponent : AndroidInjector<BaseApplication> {
@Component.Factory
interface Factory {
fun application(@BindsInstance baseApplication: BaseApplication): AppComponent
}
}
AppModule:
@Module
object AppModule {
@Singleton
@JvmStatic
@Provides
fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
ActivityModule:
@Module
abstract class ActivityModule {
@ContributesAndroidInjector(modules = [ViewModelBuilder::class, NetworkModule::class])
internal abstract fun getHomeActivity(): HomeActivity
@ContributesAndroidInjector(modules = [ViewModelBuilder::class])
internal abstract fun provideLoginActivity(): LoginActivity
@Binds
@IntoMap
@ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(loginViewModel: LoginViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel
}
NetworkModule:
@Module
object NetworkModule {
@JvmStatic
@Provides
fun getApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
ViewModelFactory:
class ViewModelFactory @Inject constructor(
private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("Unknown model class: $modelClass")
}
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
@Module
internal abstract class ViewModelBuilder {
@Binds
internal abstract fun bindViewModelFactory(
factory: ViewModelFactory
): ViewModelProvider.Factory
}
@Target(
AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
HomeViewModel:
class HomeViewModel @Inject constructor(private val apiService: ApiService) : ViewModel() {
val todoLiveData: LiveData<Todo> = liveData(Dispatchers.IO) {
val response: Todo = apiService.getTodo(1)
emit(response)
}
}
Error:
error: [Dagger/MissingBinding] com.sagar.daggertest.repository.network.ApiService cannot be provided without an @Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.sagar.daggertest.BaseApplication> {
^
A binding with matching key exists in component: com.sagar.daggertest.di.HomeActivityModule_GetHomeActivity$app_debug.HomeActivitySubcomponent
com.sagar.daggertest.repository.network.ApiService is injected at
com.sagar.daggertest.HomeViewModel(apiService)
com.sagar.daggertest.HomeViewModel is injected at
com.sagar.daggertest.di.HomeActivityModule.bindHomeViewModel(homeViewModel)
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.sagar.daggertest.di.ViewModelFactory(creators)
com.sagar.daggertest.di.ViewModelFactory is injected at
com.sagar.daggertest.di.ViewModelBuilder.bindViewModelFactory$app_debug(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.sagar.daggertest.LoginActivity.viewModelFactory
com.sagar.daggertest.LoginActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.sagar.daggertest.di.AppComponent → com.sagar.daggertest.di.HomeActivityModule_ProvideLoginActivity$app_debug.LoginActivitySubcomponent]
Upvotes: 0
Views: 379
Reputation: 1820
@ContributesAndroidInjector
creates a subcomponent under the hood ( which is HomeActivityModule_GetHomeActivity$app_debug.HomeActivitySubcomponent
in logs ).
In your ActivityModule
, you're trying to provide HomeViewModel
for map in ViewModelFactory
, which is also injected at LoginActivity
. But due to HomeViewModel
needing ApiService
and your ApiService
is in NetworkModule
which is scoped to subcomponent dagger generated - it fails.
Solution would be moving your multibinding to corresponding scope. By doing so, you're taking HomeViewModel
out of map which is injected in LoginActivity
, so it won't complain.
You can create a new module, let's say ViewModelModule
and put your provider there:
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel
}
and pass it along with other modules to HomeActivity
's contributor:
@ContributesAndroidInjector(modules = [ViewModelBuilder::class, NetworkModule::class, ViewModelModule::class])
internal abstract fun getHomeActivity(): HomeActivity
Upvotes: 3