I'm trying to learn dagger and kotlin and mvvm in one so please forgive me if this question is odd.
If I have a NetworkModule, which basically provides retrofit to the app, I think it would be a good idea to pass in the base url for which we want to build retrofit. I can do it the old way by passing it in via the App's component build function, but can't figure out how to do it via the @Component.Builder method. Attempt:
App.kt
DaggerAppComponent.builder()
.application(this)
.networkModule(BuildConfig.BASE_URL)
.build()
.inject(this)
AppComponent.kt
@Singleton
@Component(modules = arrayOf(
AppModule::class,
NetworkModule::class,
AndroidInjectionModule::class,
ActivityBuilder::class))
interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
@BindsInstance
fun networkModule(baseUrl: String): Builder
fun build(): AppComponent
}
fun inject(app: App)
}
NetworkModule.kt
@Module
class NetworkModule(baseUrl: String) {
//Attempt to force a public setter for Dagger
var baseUrl: String = baseUrl
set
@Provides
@Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor { message -> Timber.d(message) }
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return loggingInterceptor
}
@Provides
@Singleton
fun provideOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
val httpClientBuilder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) httpClientBuilder.addInterceptor(httpLoggingInterceptor)
return httpClientBuilder.build()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
}
The error is quite clear but I still don't understand what it means :)
@Component.Builder is missing setters for required modules or components: [core.sdk.di.module.NetworkModule]
. I tried to force the module to create a public setter for the base url (although I think that's not needed).
To create a component all the modules need to be supplied. Now Dagger can create any module that has a no-arg constructor itself, so that you don't have to pass it in.
@Module
class NetworkModule(baseUrl: String) { /* ... */ }
You declare a module that has a parameter in it's construcot, hence Dagger can't construct it. This is what the error tries to tell you:
@Component.Builder is missing setters for required modules or components: [core.sdk.di.module.NetworkModule]
You have a module that has to be added manually (because Dagger can't construct it) but your @Component.Builder
does not have a way to include it.
What you provide is a binding for String
as your base url, which is not the very best idea, since you might want to use multiple objects of String
in your project. You should consider using a @Qualifier
, but more on this later.
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
// binds a `String` to the component
@BindsInstance
fun networkModule(baseUrl: String): Builder
// no method to set the NetworkModule!
fun build(): AppComponent
}
To resolve your issue you have 2 options. You can either create a setter and pass the url to the module constructor yourself, then set the module on the component (1) or you can bind the url and simply consume it in your module, removing the parameter from the constructor (2).
This is the somewhat easier method since you just create the module yourself and pass it into the Dagger component builder. All you have to do is replace the binding of your String with a setter for the module instead.
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
// add the module manually
fun networkModule(networkModule: NetworkModule): Builder
fun build(): AppComponent
}
When creating the component you now have to add the module to the builder.
builder
.application(app)
.networkModule(NetworkModule(MY_BASE_URL)) // create the module yourself
.build()
To bind the url to the component and simply use it in your module—as it seems you intended— you have to remove the parameter from your constructor.
@Module
class NetworkModule() { /* ... */ } // no-arg constructor
By doing so Dagger can now create the module and you'll be rid of that error. Instead of putting the url in the constructor we want to get it passed in by Dagger, but as mentioned above using String
is not optimal, since it literally tells Dagger that there you want that value for any String
. You should use a qualifier to add a proper distinction, e.g. @Named("baseUrl") String
.
More information about qualifiers
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
// bind a `String` named "baseUrl" to this component
@BindsInstance
fun baseUrl(@Named("baseUrl") baseUrl: String): Builder
fun build(): AppComponent
}
Dagger now knows that there is a @Named("baseUrl") String
that we now also can use in our module.
@Module
class NetworkModule() {
// other methods omitted
@Provides
@Singleton
fun provideRetrofit(
// request a String named baseUrl from Dagger
@Named("baseUrl") baseUrl: String,
okHttpClient: OkHttpClient
): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
// ...
.build()
}
And that's it.
@Component.Builder is missing setters for required modules or components: [core.sdk.di.module.NetworkModule]
The error is very literal, the builder is missing a method to specify the exact type NetworkModule
which it does not know how to build otherwise.
@BindsInstance
fun networkModule(baseUrl: String): Builder
This piece of code binds the type String
, it does not have an effect on the creation of NetworkModule
. Since NetworkModule
cannot be injected and does not have a zero-argument constructor it must be set using the type of the builder directly:
fun networkModule(module: NetworkModule): Builder
In App.kt
you then rewrite the builder with .networkModule(NetworkModule(BuildConfig.BASE_URL))
and it should construct successfully.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With