sagar suri
sagar suri

Reputation: 4731

kotlin + Dagger 2: ApiService cannot be provided without an @Provides-annotated method

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

Answers (1)

Mel
Mel

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

Related Questions