0
votes

I've a problem in injecting Strings in dagger

This is my implementation

@Singleton
@Component(
    modules = [AndroidInjectionModule::class,
        ActivityBuilder::class,
        ViewModelModule::class,
        NetModule::class,
        AppModule::class]
)
interface AppComponent : AndroidInjector<DaggerApplication> {

    fun inject(theDApplication: TFTScreenApplication)

    override fun inject(instance: DaggerApplication)

    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        @BindsInstance
        @Named(Constants.API_URL_KEY)
        fun apiUrl(apiUrl: String): Builder

        @BindsInstance
        @Named(Constants.SOCKET_URL_KEY)
        fun socketUrl(socketUrl: String): Builder

        fun build(): AppComponent
    }
}

Then in Net Module

@Module
abstract class NetModule {

    @Binds
    @Named(Constants.API_URL_KEY)
    abstract fun provideApiUrl(apiUrl: String): String

    @Binds
    @Named(Constants.SOCKET_URL_KEY)
    abstract fun provideSocketUrl(socketUrl: String): String

    @Module
    companion object {

        @Provides
        @Reusable
        @JvmStatic
        fun providesOkHttpClient(): OkHttpClient {
            return OkHttpClient.Builder()
                .build()
        }

        @Provides
        @Reusable
        @JvmStatic
        fun providesGson(): Gson {
            val gsonBuilder = GsonBuilder()
            gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
            return gsonBuilder.create()
        }

        @Provides
        @Reusable
        @JvmStatic
        fun providesRetrofit(
            @Named(Constants.API_URL_KEY) apiUrl: String, gson: Gson
        ): Retrofit {
            return Retrofit.Builder()
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .baseUrl(apiUrl)
                .build()
        }

        @Provides
        @Reusable
        @JvmStatic
        fun providesBackOffStrategy(): BackoffStrategy {
            return ExponentialWithJitterBackoffStrategy(5000, 5000)
        }

        @Provides
        @Reusable
        @JvmStatic
        fun providesLifeCycle(application: Application): Lifecycle {
            return AndroidLifecycle.ofApplicationForeground(application)
        }

        @Provides
        @Reusable
        @JvmStatic
        fun providesScarlet(
            @Named(Constants.SOCKET_URL_KEY) socketUrl: String, okHttpClient: OkHttpClient,
            backoffStrategy: BackoffStrategy,
            lifecycle: Lifecycle
        ): Scarlet {
            return Scarlet.Builder()
                .webSocketFactory(okHttpClient.newWebSocketFactory(socketUrl))
                .addMessageAdapterFactory(MoshiMessageAdapter.Factory())
                .addStreamAdapterFactory(RxJava2StreamAdapterFactory())
                .backoffStrategy(backoffStrategy)
                .lifecycle(lifecycle)
                .build()
        }

        @Provides
        @Reusable
        @JvmStatic
        fun providesCoinSocket(scarlet: Scarlet): SocketService {
            return scarlet.create(SocketService::class.java)
        }

        @Provides
        @Reusable
        @JvmStatic
        fun providesPISAPIs(retrofit: Retrofit): PISAPIs {
            return retrofit.create(PISAPIs::class.java)
        }
    }

The error is

error: [Dagger/DuplicateBindings] java.lang.String is bound multiple times: public abstract interface AppComponent extends dagger.android.AndroidInjector { ^ @org.jetbrains.annotations.NotNull @Named("API_URL") @BindsInstance com.example.tftscreen.common.di.component.AppComponent.Builder com.example.tftscreen.common.di.component.AppComponent.Builder.apiUrl(String) @org.jetbrains.annotations.NotNull @Named("SOCKET_URL") @BindsInstance com.example.tftscreen.common.di.component.AppComponent.Builder com.example.tftscreen.common.di.component.AppComponent.Builder.socketUrl(String) java.lang.String is injected at com.example.tftscreen.common.di.module.NetModule.provideSocketUrl(socketUrl) @javax.inject.Named("SOCKET_URL") java.lang.String is injected at com.example.tftscreen.common.di.module.NetModule.providesScarlet(socketUrl, …) com.tinder.scarlet.Scarlet is injected at com.example.tftscreen.common.di.module.NetModule.providesCoinSocket(scarlet) com.example.tftscreen.pis.SocketService is injected at com.example.tftscreen.pis.data.PISRemoteRepository(socketService, …) com.example.tftscreen.pis.data.PISRemoteRepository is injected at com.example.tftscreen.pis.PISViewModel(pisRemoteRepository) com.example.tftscreen.pis.PISViewModel is injected at com.example.tftscreen.common.di.module.ViewModelModule.bindPISViewModel(pisViewModel) java.util.Map,javax.inject.Provider> is injected at com.example.tftscreen.common.presentationLayer.ViewModelFactory(creators) com.example.tftscreen.common.presentationLayer.ViewModelFactory is injected at com.example.tftscreen.common.di.module.ViewModelModule.provideViewModelFactory(viewModelFactory) androidx.lifecycle.ViewModelProvider.Factory is injected at com.example.tftscreen.pis.PISActivity.viewModelFactory com.example.tftscreen.pis.PISActivity is injected at dagger.android.AndroidInjector.inject(T) [com.example.tftscreen.common.di.component.AppComponent → com.example.tftscreen.common.di.module.ActivityBuilder_BindMainActivity.PISActivitySubcomponent] It is also requested at: com.example.tftscreen.common.di.module.NetModule.provideApiUrl(apiUrl)

1
did you try removing your methods marked with @Binds? - coroutineDispatcher
I cannot because by doing the the Strings won't be provided java.lang.String cannot be provided without an @Provides-annotated method - Nour El-Deen Abou El-Kassem
ok , but why not use the @Provide for those? I'm a little not clear on how you use the @Binds annotation . It usualy is used on providing interfaces where classes implement them. - coroutineDispatcher
because i use those as abstract methods and we cannot use provides with abstract methods and i use it inside app component because the app component is an interface - Nour El-Deen Abou El-Kassem
by using BindsInstance you wont need to have Provider methods in your module. Are you passing those 2 strings when you instantiate your dagger component? Also, what happens if you annotate the parameters with the @Named(...) annotation instead of the methods? - Benny

1 Answers

1
votes

@BindsInstance, is most useful when you have dependencies that need to be introduced into the object graph at runtime.

@Binds is used when you need to bind an interface to an implementation and dagger can construct the concrete implementation for you (through an @Inject constructor)

It doesn't look like your Constants.SOCKET_URL_KEY & Constants.API_URL_KEY meet either of these criteria, so...

If it's true that these are available at compile time and you want dagger to provide these, then the easiest way to do that would be to add the following to your module:

@Provides
@JvmStatic
@Named(Constants.API_URL_KEY)
fun providesApiUrlKey(): String {
  return "YOUR_API_KEY"      
}

and remove:

@BindsInstance
@Named(Constants.API_URL_KEY)
fun apiUrl(apiUrl: String): Builder

In either situation (available at runtime or compile time), you'll need to remove this:

@Binds
@Named(Constants.API_URL_KEY)
abstract fun provideApiUrl(apiUrl: String): String

@Binds
@Named(Constants.SOCKET_URL_KEY)
abstract fun provideSocketUrl(socketUrl: String): String