Search code examples
dependency-injectionretrofitdagger-2

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


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)
                })
        }
    }

}

Solution

  • When using qualifiers, you need to place the qualifier annotation in two places:

    • Where the object is bound: a @Binds or @Provides method, a @BindsInstance method in a builder, or a @BindsInstance parameter in a factory.
    • Where the object is used: a parameter to a @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()
        }