Mohammad Sianaki
Mohammad Sianaki

Reputation: 1731

Dagger2 : Cannot be provided without an @Inject constructor or an @Provides-annotated method

I've just refactored my dagger code to make it scalable and move all core stuff to a separate module called di.

Now when I try to inject my dependencies in the app module I got this :

[Dagger/MissingBinding] retrofit2.Retrofit cannot be provided without an @Inject constructor or an @Provides-annotated method.
public abstract interface ResetPasswordComponent {
                ^
      retrofit2.Retrofit is injected at
          com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordNetworkModule.providerResetPasswordAPI(retrofit)
      com.sahra.oms.ibshop.data.remote.service.ResetPasswordService is injected at
          com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueRepositoryImpl(resetPasswordService)
      com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueRepositoryImpl is injected at
          com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordModule.bindUniqueIdRepository(uniqueRepositoryImpl)
      com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueIdRepository is injected at
          com.sahra.oms.ibshop.features.resetpassword.uniqueid.CheckUniqueIDViewModel(repository)
      com.sahra.oms.ibshop.features.resetpassword.uniqueid.CheckUniqueIDViewModel is injected at
          com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordModule.bindCheckIdViewModel(checkUniqueIDViewModel)
      java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
          com.sahra.oms.ibishop.di.util.ViewModelFactory(viewModelsMap)
      com.sahra.oms.ibishop.di.util.ViewModelFactory is injected at
          com.sahra.oms.ibshop.di.ViewModelBuilder.bindViewModelFactory(arg0)
      androidx.lifecycle.ViewModelProvider.Factory is injected at
          com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment.viewModelFactory
      com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment is injected at
          com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordComponent.inject(com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment)

ResetPasswordService is just a Retrofit interface.

Here is my code:

AppComponent

@Singleton
@AppScope
@Component
interface AppComponent {


    fun provideContextComponent(): ContextComponent
    fun provideNetworkComponent(): NetworkComponent
    fun provideSharedPrefComponent(): SharedPreferencesComponent

    fun inject(app: Application)

    @Component.Factory
    interface Factory {

        fun create(
            @BindsInstance
            context: ContextComponent,
            @BindsInstance
            network: NetworkComponent,
            @BindsInstance
            sharedPrefs: SharedPreferencesComponent
        ): AppComponent
    }
}

NetworkComponent :

@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class NetworkScope


@NetworkScope
@Component(
    dependencies = [ContextComponent::class],
    modules = [OkHttpModule::class, AuthBinderModule::class]
)
interface NetworkComponent {

    fun provideOkHttp(): OkHttpClient
    fun provideRetrofit(): Retrofit
    fun provideGson(): GsonConverterFactory

}

OkHttpModule :

@Module
object OkHttpModule {
    private const val BASE_URL = "base_url"

    @Provides
    @JvmStatic
    fun provideLoggingInterceptor(): HttpLoggingInterceptor {
        return HttpLoggingInterceptor(
            HttpLoggingInterceptor.Logger { message -> Log.d("<<<network>>>", message) }).apply {
            level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
        }
    }

    @Provides
    @JvmStatic
    fun provideChuckInterceptor(app: Application): ChuckInterceptor = ChuckInterceptor(app)

    @Provides
    @JvmStatic
    fun provideOkhttpCache(app: Application): Cache =
        Cache(app.cacheDir, 50_000_000)

    @Provides
    @NetworkScope
    @JvmStatic
    fun provideClient(
        loggingInterceptor: HttpLoggingInterceptor,
        chuckInterceptor: ChuckInterceptor,
        authInterceptor: Interceptor,
        cache: Cache
    ): OkHttpClient {
        return OkHttpClient.Builder()
            .cache(cache)
            .addInterceptor(loggingInterceptor)
            .addInterceptor(authInterceptor)
            .addInterceptor(chuckInterceptor)
            .build()
    }

    @Provides
    @NetworkScope
    @JvmStatic
    fun provideGson() = Gson()


    @Provides
    @JvmStatic
    fun provideGsonConverter(gson: Gson) = GsonConverterFactory.create(gson)

    @Provides
    @NetworkScope
    @JvmStatic
    fun provideRetrofit(
        gsonConverterFactory: GsonConverterFactory,
        client: Lazy<OkHttpClient>
    ): Retrofit = Retrofit.Builder()
        .callFactory { request -> client.get().newCall(request) }
        .baseUrl(BASE_URL)
        .addConverterFactory(gsonConverterFactory)
        .build()
}

Here is how I try to inject the dependencies:

@FeatureScope
@Component(
    dependencies = [AppComponent::class],
    modules = [
        ResetPasswordNetworkModule::class,
        ResetPasswordModule::class,
        ViewModelBuilder::class
    ]
)
interface ResetPasswordComponent {
    fun inject(newPasswordFragment: NewPasswordFragment)
    fun inject(checkUniqueIDFragment: CheckUniqueIDFragment)

    @Component.Builder
    interface Builder {
        fun coreComponent(appComponent: AppComponent): Builder
        fun build(): ResetPasswordComponent
    }
}
@Module
abstract class ResetPasswordModule {

    @Binds
    abstract fun bindResetPasswordRepository(resetPasswordRepositoryImpl: ResetPasswordRepositoryImpl): ResetPasswordRepository

    @Binds
    abstract fun bindUniqueIdRepository(uniqueRepositoryImpl: UniqueRepositoryImpl): UniqueIdRepository

    @Binds
    @IntoMap
    @ViewModelKey(CheckUniqueIDViewModel::class)
    abstract fun bindCheckIdViewModel(checkUniqueIDViewModel: CheckUniqueIDViewModel): ViewModel


    @Binds
    @IntoMap
    @ViewModelKey(NewPasswordViewModel::class)
    abstract fun bindNewPasswordViewModel(newPasswordViewModel: NewPasswordViewModel): ViewModel
}
@Module
object ResetPasswordNetworkModule {
    @Provides
    @JvmStatic
    @FeatureScope
    fun provideUserAPI(
        retrofit: Retrofit
    ): ResetPasswordService = retrofit.create(ResetPasswordService::class.java)
}

and here is my repository code:

class ResetPasswordRepositoryImpl @Inject constructor(
    private val resetPasswordService: ResetPasswordService
) : ResetPasswordRepository {
}

Fragment :

class NewPasswordFragment{
    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
}
class NewPasswordViewModel @Inject constructor(
    private val repository: ResetPasswordRepository
)

Thanks in advance.

Upvotes: 1

Views: 1412

Answers (1)

Nitrodon
Nitrodon

Reputation: 3435

Your app component factory looks like this:

    fun create(
        @BindsInstance
        context: ContextComponent,
        @BindsInstance
        network: NetworkComponent,
        @BindsInstance
        sharedPrefs: SharedPreferencesComponent
    ): AppComponent

This provides access to an instance of NetworkComponent, so any @Provides, @Binds, or @Inject that requires a NetworkComponent can get one. It does not, however, give direct access to NetworkComponent's object graph.

NetworkComponent already exposes Retrofit, so you can certainly get one if you have access to the component. However, this process is not automatic, and in your setting requires a @Provides method.

@Provides
fun provideRetrofit(component: NetworkComponent): Retrofit = component.provideRetrofit()

This is more convoluted that it needs to be. A better way to accomplish this is to make NetworkComponent a dependency of AppComponent (or just use its modules and drop the network component entirely), then expose Retrofit in AppComponent.

// using multiple scoped dependencies requires Dagger 2.27
@AppScope
@Component(dependencies = [ContextComponent::class, NetworkComponent::class, SharedPreferencesComponent::class])
interface AppComponent {
    fun provideRetrofit(): Retrofit
    // ...
}

Upvotes: 1

Related Questions