Reputation: 1731
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
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