For learning, I want to implement Dagger2 for Dependency Injection in a simple project. I read through the google Codelab code sample to have a basic idea of Dagger2. Then I read through some medium blog and sample Github repo which has implemented Dagger2 for dependency injection in the project. Then I started a demo project and try to implement Dagger2 with Retrofit. After Implementation I got an unexpected build failed error having "error: [Dagger/MissingBinding] com.aomi.mybase.data.remote.testimonial.TestimonialRestService cannot be provided without an @Provides-annotated method." TestimonialService is the Api related service. For that reason, I can't annotate with @Provide or @Binds annotation. I really don't know what to do to solve this issue
The error log is screenshot is given bellow https://i.sstatic.net/nqjBj.jpg
Let me share some of my code so that you can have a look where the problem actually have
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)
})
}
}
}
When using qualifiers, you need to place the qualifier annotation in two places:
@Binds
or @Provides
method, a @BindsInstance
method in a builder, or a @BindsInstance
parameter in a factory.@Binds
or @Provides
method, a parameter to an @Inject
constructor or method, or an @Inject
field/property.A dependency can only be provided if the qualifier at the use site matches the qualifier on the @Binds
/@Provides
method. For this purpose, "no qualifier" is a type of qualifier.
Since you want to use a @Type("Basic")
-qualified Retrofit
to provide an unqualified TestimonialRestService
, this means the parameter should be qualified and the method itself should be unqualified:
@JvmStatic
@Singleton
@Provides
fun provideTestimonialService(@Type("Basic") retrofit: Retrofit): TestimonialRestService {
return retrofit.create(TestimonialRestService::class.java)
}
When you fix this, you'll notice that the provideXxxRetrofit
methods also have errors for the same reason: they are both looking for an unqualified OkHttpClient
, but the only OkHttpClient
bindings in your graph have qualifiers. Those errors can be fixed in the same way:
@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()
}