How to solve error: [Dagger/MissingBinding] while working With Retrofit with Dagger2

How to solve error: [Dagger/MissingBinding] while working With Retrofit with Dagger2

为了学习,我想在一个简单的项目中实现Dagger2 for Dependency Injection。我通读了 google Codelab 代码示例,以了解 Dagger2 的基本概念。然后我阅读了一些媒体博客和示例 Github repo,它已经在项目中实现了 Dagger2 以进行依赖注入。然后我开始了一个演示项目并尝试用 Retrofit 实现 Dagger2。实施后,我收到了一个意外的构建失败错误,其中 "error: [Dagger/MissingBinding] com.aomi.mybase.data.remote.testimonial.TestimonialRestService cannot be provided without an @Provides-annotated method." TestimonialService 是 Api 相关服务。因此,我无法使用 @Provide 或 @Binds 注释进行注释。我真的不知道该怎么做才能解决这个问题

错误日志截图如下 https://imgur.com/a/0qQXLbN

让我分享一些我的代码,这样你就可以看看问题到底出在哪里

Qualifier.kt

@Qualifier
@MustBeDocumented
@Retention(RUNTIME)
annotation class Type(val type: String = "")

InterceptorModule.kt

@Module
class InterceptorModule {

    @Module
    companion object {

        @JvmStatic
        @Singleton
        @Provides
        fun provideLoggingInterceptor(): HttpLoggingInterceptor {
            return HttpLoggingInterceptor().apply {
                level = if (BuildConfig.DEBUG) BODY else NONE
            }
        }

        @JvmStatic
        @Singleton
        @Type("Basic")
        @Provides
        fun provideBasicInterceptor(): Interceptor {
            val basicAuthCredential = Credentials.basic(
                "username",
                "password"
            )

            try {
                return Interceptor {
                    val request = it.request()

                    it.proceed(
                        request.newBuilder()
                            .header("Accept", "application/json")
                            .header("Content-Type", "application/json")
                            .header("Authorization", basicAuthCredential)
                            .build()
                    )
                }
            } catch (exception: Exception) {
                throw Exception(exception.message)
            }
        }

        @JvmStatic
        @Singleton
        @Type("Bearer")
        @Provides
        fun provideAuthInterceptor(appContext: Context): Interceptor {
            val accessToken = AppPreferenceImpl(appContext).accessToken

            try {
                return Interceptor {
                    val request = it.request()

                    it.proceed(
                        request.newBuilder()
                            .header("Accept", "application/json")
                            .header("Content-Type", "application/json")
                            .header("Authorization", "Bearer $accessToken")
                            .build()
                    )
                }
            } catch (exception: Exception) {
                throw Exception(exception.message)
            }
        }

    }
}

NetworkModule.kt

@Module(
    includes = [
        InterceptorModule::class
    ]
)
abstract class NetworkModule {

    @Module
    companion object {

        private const val BASE_URL = BuildConfig.BASE_URL
        private const val TIME_OUT = 60L

        @JvmStatic
        @Singleton
        @Type("Basic")
        @Provides
        fun provideBasicRetrofit(okHttpClient: OkHttpClient): Retrofit {
            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(okHttpClient)
                .callbackExecutor { Logger.d("returning") }
                .build()
        }

        @JvmStatic
        @Singleton
        @Type("Basic")
        @Provides
        fun provideBasicOkHttpClient(loggingInterceptor: HttpLoggingInterceptor, basicInterceptor: Interceptor): OkHttpClient {
            return OkHttpClient.Builder()
                .connectTimeout(TIME_OUT, SECONDS)
                .readTimeout(TIME_OUT, SECONDS)
                .writeTimeout(TIME_OUT, SECONDS)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(basicInterceptor)
                .build()
        }

        @JvmStatic
        @Singleton
        @Type("Bearer")
        @Provides
        fun provideBearerRetrofit(okHttpClient: OkHttpClient): Retrofit {
            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(okHttpClient)
                .callbackExecutor { Logger.d("returning") }
                .build()
        }

        @JvmStatic
        @Singleton
        @Type("Bearer")
        @Provides
        fun provideBearerOkHttpClient(loggingInterceptor: HttpLoggingInterceptor, authInterceptor: Interceptor): OkHttpClient {
            return OkHttpClient.Builder()
                .connectTimeout(TIME_OUT, SECONDS)
                .readTimeout(TIME_OUT, SECONDS)
                .writeTimeout(TIME_OUT, SECONDS)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(authInterceptor)
//                .authenticator(ServiceAuthenticator())
                .build()
        }

    }
}

ServiceModule.kt

@Module
abstract class ServiceModule {

    @Module
    companion object {

        @JvmStatic
        @Singleton
        @Type("Basic")
        @Provides
        fun provideTestimonialService(retrofit: Retrofit): TestimonialRestService {
            return retrofit.create(TestimonialRestService::class.java)
        }

    }
}

RepositoryModule.kt

@Module
abstract class RepositoryModule {

    @Binds
    abstract fun provideTestimonialRepository(repo: TestimonialRepositoryImpl): TestimonialRepository

}

TestimonialRepository.kt

interface TestimonialRepository {
    fun getTestimonials(): Flowable<ArrayList<Testimonial>>
}

TestimonialRepositoryImpl.kt

class TestimonialRepositoryImpl @Inject constructor(
    private val testimonialDataSource: TestimonialDataSource
) : TestimonialRepository {

    override fun getTestimonials(): Flowable<ArrayList<Testimonial>> {
        return testimonialDataSource.getTestimonialResponse().map { it.testimonialList }
    }

}

TestimonialDataSource.kt

class TestimonialDataSource @Inject constructor(
    private val testimonialRestService: TestimonialRestService
) {

    fun getTestimonialResponse(): Flowable<TestimonialResponse> {
        return testimonialRestService.getTestimonialResponse().onResponse()
    }

}

TestimonialRestService.kt

interface TestimonialRestService {

    @GET("static/testimonials")
    fun getTestimonialResponse(): Flowable<Response<TestimonialResponse>>

}

WelcomeViewModel.kt

class WelcomeViewModel @Inject constructor(
    private val repository: TestimonialRepository
) : BaseViewModel() {

    var testimonials = MutableLiveData<ArrayList<Testimonial>>()

    fun getTestimonials() {
        if(testimonials.value == null) {
            compositeDisposable += repository.getTestimonials()
                .performOnBackgroundOutputOnMain()
                .doOnSubscribe { loader.value = true }
                .doAfterTerminate { loader.value = false }
                .subscribe({
                    Logger.d(it)
                    testimonials.value = it
                }, {
                    handleException(it)
                })
        }
    }

}

使用限定符时,需要在两个地方放置限定符注解:

  • 对象的绑定位置:@Binds@Provides 方法,构建器中的 @BindsInstance 方法,或工厂中的 @BindsInstance 参数。
  • 对象的使用位置:参数@Binds@Provides方法,@Inject构造函数或方法的参数, 或 @Inject field/property.

仅当使用站点的限定符与 @Binds/@Provides 方法上的限定符匹配时,才能提供依赖项。为此,"no qualifier" 是一种限定符。

既然你想用一个@Type("Basic")限定的Retrofit来提供一个非限定的TestimonialRestService,这意味着参数应该是限定的,而方法本身应该是非限定的:

    @JvmStatic
    @Singleton
    @Provides
    fun provideTestimonialService(@Type("Basic") retrofit: Retrofit): TestimonialRestService {
        return retrofit.create(TestimonialRestService::class.java)
    }

修复此问题后,您会注意到 provideXxxRetrofit 方法也有错误,原因相同:它们都在寻找不合格的 OkHttpClient,但唯一的 OkHttpClient 图中的绑定具有限定符。这些错误可以用同样的方式修复:

    @JvmStatic
    @Singleton
    @Type("Basic")
    @Provides
    fun provideBasicRetrofit(@Type("Basic") okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .callbackExecutor { Logger.d("returning") }
            .build()
    }

虽然@Nitrodon的回答是对的,但是我根据他的回答做了更符合实际的,这样任何陷入这个问题的人都可以很容易地理解它。下面是更好理解的代码

Constant.kt

object Network {
    const val BASIC = "Basic"
    const val BEARER = "Bearer"
} 

Qualifier.kt

@Qualifier
@MustBeDocumented
@Retention(RUNTIME)
annotation class InterceptorType(val type: String = "")

@Qualifier
@MustBeDocumented
@Retention(RUNTIME)
annotation class OkHttpClientType(val type: String = "")

@Qualifier
@MustBeDocumented
@Retention(RUNTIME)
annotation class RetrofitType(val type: String = "")

InterceptorModule.kt

@Module
class InterceptorModule {

    companion object {

        @Singleton
        @Provides
        fun provideLoggingInterceptor(): HttpLoggingInterceptor {
            return HttpLoggingInterceptor().apply {
                level = if (BuildConfig.DEBUG) BODY else NONE
            }
        }

        @Singleton
        @InterceptorType(BASIC)
        @Provides
        fun provideBasicInterceptor(): Interceptor {
            val basicAuthCredential = Credentials.basic(
                "ct_android",
                "$2y$12$ej.DK5rJIZjF9FokTWErDeDylA7N.4apw0FZ2FllcK53KEYZqDryO"
            )

            try {
                return Interceptor {
                    val request = it.request()

                    it.proceed(
                        request.newBuilder()
                            .header("Accept", "application/json")
                            .header("Content-Type", "application/json")
                            .header("Authorization", basicAuthCredential)
                            .build()
                    )
                }
            } catch (exception: Exception) {
                throw Exception(exception.message)
            }
        }

        @Singleton
        @InterceptorType(BEARER)
        @Provides
        fun provideAuthInterceptor(appContext: Context): Interceptor {
            val accessToken = AppPreferenceImpl(appContext).accessToken

            try {
                return Interceptor {
                    val request = it.request()

                    it.proceed(
                        request.newBuilder()
                            .header("Accept", "application/json")
                            .header("Content-Type", "application/json")
                            .header("Authorization", "Bearer $accessToken")
                            .build()
                    )
                }
            } catch (exception: Exception) {
                throw Exception(exception.message)
            }
        }

    }
}

NetworkModule.kt

@Module(
    includes = [
        InterceptorModule::class
    ]
)
abstract class NetworkModule {

    companion object {
        private const val BASE_URL = BuildConfig.BASE_URL
        private const val TIME_OUT = 60L

        @Singleton
        @RetrofitType(BASIC)
        @Provides
        fun provideBasicRetrofit(@OkHttpClientType(BASIC) okHttpClient: OkHttpClient): Retrofit {
            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(okHttpClient)
                .callbackExecutor { Logger.d("returning") }
                .build()
        }

        @Singleton
        @OkHttpClientType(BASIC)
        @Provides
        fun provideBasicOkHttpClient(loggingInterceptor: HttpLoggingInterceptor, @InterceptorType(BASIC) basicInterceptor: Interceptor): OkHttpClient {
            return OkHttpClient.Builder()
                .connectTimeout(TIME_OUT, SECONDS)
                .readTimeout(TIME_OUT, SECONDS)
                .writeTimeout(TIME_OUT, SECONDS)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(basicInterceptor)
                .build()
        }

        @Singleton
        @RetrofitType(BEARER)
        @Provides
        fun provideBearerRetrofit(@OkHttpClientType(BEARER) okHttpClient: OkHttpClient): Retrofit {
            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(okHttpClient)
                .callbackExecutor { Logger.d("returning") }
                .build()
        }

        @Singleton
        @OkHttpClientType(BEARER)
        @Provides
        fun provideBearerOkHttpClient(loggingInterceptor: HttpLoggingInterceptor, @InterceptorType(BEARER) authInterceptor: Interceptor): OkHttpClient {
            return OkHttpClient.Builder()
                .connectTimeout(TIME_OUT, SECONDS)
                .readTimeout(TIME_OUT, SECONDS)
                .writeTimeout(TIME_OUT, SECONDS)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(authInterceptor)
//                .authenticator(ServiceAuthenticator())
                .build()
        }

    }
}

ServiceModule.kt

@Module
abstract class ServiceModule {

    companion object {

        @Singleton
        @Provides
        fun provideTestimonialService(@RetrofitType(BASIC) retrofit: Retrofit): TestimonialRestService {
            return retrofit.create(TestimonialRestService::class.java)
        }

    }
}