mars885
mars885

Reputation: 83

Dagger multibinding with a custom qualifier

I have the following interface called SettingHandler that is responsible for handling events related to a particular setting inside the Android app.

interface SettingHandler {

    fun onHandleEvent(event: SettingEvent)

    fun candleHandleEvent(event: SettingEvent): Boolean

    fun getSettingId(): SettingId

}

Then let's say I have the following Dagger module:

@Module
object SettingsModule {

    @Provides
    fun provideSettingPresenter(
        view: SettingsContract.View,
        settingHandlers: Set<@JvmSuppressWildcards SettingHandler>
    ): SettingsPresenter {
        //
    }

    @Provides
    @IntoSet
    fun provideSettingHandlerA(
        dependencyA: A,
        dependencyB: B
    ): SettingHandler {
        // 
    }

    @Provides
    @IntoSet
    fun provideSettingHandlerB(
        settingHandlerC: Provider<SettingHandler>
    ): SettingHandler {
        //
    }

    @Provides
    @IntoSet
    fun provideSettingHandlerC(
        settingHandlerB: Provider<SettingHandler>
    ): SettingHandler {
        //
    }

}

As you can see, nothing too special here, expect for the SettingHandlerB and SettingHandlerC provider methods. They both depend on each other, so I've decided to resolve this circular dependency using the Dagger supplied Provider class. Since I require a concrete implementation of a setting handler (SettingHandlerC for the SettingHandlerB and SettingHandlerB for the SettingHandlerC), I now need to differentiate between them. That's where a @Qualifier annotation comes into play. I created the following qualifier annotation to differentiate among different implementations.

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
annotation class SettingHandlerType(val id: SettingId)

SettingId is basically an enumeration that contains all possible setting constants. So now my SettingHandlers module looks as follows:

@Module
object SettingsModule {

    @Provides
    fun provideSettingPresenter(
        view: SettingsContract.View,
        settingHandlers: Set<@JvmSuppressWildcards SettingHandler>
    ): SettingsPresenter {
        //
    }

    @Provides
    @SettingHandlerType(SettingId.A)
    fun provideSettingHandlerA(
        dependencyA: A,
        dependencyB: B
    ): SettingHandler {
        //
    }

    @Provides
    @SettingHandlerType(SettingId.B)
    fun provideSettingHandlerB(
        @SettingHandlerType(SettingId.C)
        settingHandlerC: Provider<SettingHandler>
    ): SettingHandler {
        //
    }

    @Provides
    @SettingHandlerType(SettingId.C)
    fun provideSettingHandlerC(
        @SettingHandlerType(SettingId.B)
        settingHandlerB: Provider<SettingHandler>
    ): SettingHandler {
        //
    }

}

And here comes the problem. Mutlibinding now does not work, because all my SettingHandlers are annotated with a @SettingHandlerType annotation and a Set that I'm injecting into the SettingsPresenter also needs to be annotated. However, annotating it with, for example, @SettingHandlerType(SettingId.A) won't work because in that case the Set will contain only setting handlers with that particular qualifier (SettingHandlerA, in this case). How can I construct a Set data structure using multibinding because I really don't want to provide another provider method where I'll be constructing it myself?

Any help would be much appreciated. Thanks in advance.

Upvotes: 1

Views: 1800

Answers (1)

Jeff Bowman
Jeff Bowman

Reputation: 95704

You want two different types of data: Individual handlers of type @SettingHandlerType(/*...*/) SettingHandler and a set of type Set<SettingHandler>. I think the best way would be to have each definition come with a @Binds @IntoSet complement.

@Binds @IntoSet abstract fun bindSettingHandlerA(
    @SettingHandlerType(SettingId.A) handler: SettingHandler): SettingHandler

Unfortunately, since you've defined this as an object in Kotlin, it's slightly more complicated to put the necessarily-abstract @Binds methods on the same object. You may need to pursue one of these other solutions to make that topology work with your case, including the use of a companion object to capture the otherwise-static @Provides methods:

Upvotes: 1

Related Questions