kotlin + Dagger 2:没有 @Provides-annotated 方法就无法提供 ApiService

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

我已经浏览了以上标题的所有答案,但找不到解决方案。基本上我想做范围界定。我只想将 ApiService 注入 HomeViewModel。它不应该对 LoginViewModel 可用。我有以下设置和我收到的错误:

Info:如果我从 ActivityModule 中删除 provideLoginActivity(),一切正常。为什么会那样?

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
}

网络模块:

@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: [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]

@ContributesAndroidInjector 在后台创建一个 subcomponent(在日志中是 HomeActivityModule_GetHomeActivity$app_debug.HomeActivitySubcomponent)。

在您的 ActivityModule 中,您试图为 ViewModelFactory 中的地图提供 HomeViewModel,它也在 LoginActivity 中注入。但是由于 HomeViewModel 需要 ApiService 而你的 ApiServiceNetworkModule 中,它的范围是生成的子组件匕首 - 它失败了。

解决方案是将您的多重绑定移动到相应的范围。通过这样做,您将 HomeViewModel 从注入 LoginActivity 的地图中取出,因此它不会抱怨。

您可以创建一个新模块,比方说 ViewModelModule 并将您的提供商放在那里:

@Module
abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(HomeViewModel::class)
    abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel
}

并将其与其他模块一起传递给 HomeActivity 的贡献者:

@ContributesAndroidInjector(modules = [ViewModelBuilder::class, NetworkModule::class, ViewModelModule::class])
internal abstract fun getHomeActivity(): HomeActivity