10

With Dagger2 it's easy to explicitly create components and list their dependencies. But I can't seem to find a way to provide different implementations of an interface to lets say a fragment.

For example, my app has 2 production modes: paid and free. I have a PaidActivity and a FreeActivity, both of which start exactly the same Dashboard fragment with an Analytics class. For Paid I provide a PaidAnalytics implementation, for Free I provide a FreeAnalytics implementation.

With Dagger2 it's easily achieved by just listing a Paid or a Free Module in the Activity's Component (or Subcomponent).

@Module
abstract class FreeActivityModule {

    @ContributesAndroidInjector(
        modules = [
            FreeAnalyticsModule::class,
            DashboardFragmentModule::class
        ]
    )
    abstract fun injectFreeActivity(): FreeActivity

}

@Module
abstract class PaidActivityModule {

    @ContributesAndroidInjector(
        modules = [
            PaidAnalyticsModule::class,
            DashboardFragmentModule::class
        ]
    )
    abstract fun injectPaidActivity(): PaidActivity

}

@Module
abstract class DashboardFragmentModule {

    @ContributesAndroidInjector
    abstract fun injectDashboardFragment(): DashboardFragment

}

class DashboardFragment : Fragment() {

    ...

    @Inject
    lateinit var analytics: Analytics

    ...

}

With Dagger Hilt I couldn't find a way to do this.

Dmytro Karataiev
  • 1,214
  • 1
  • 14
  • 19
  • Does this answer your question? [bind interface by different concrete class in hilt?](https://stackoverflow.com/questions/65458172/bind-interface-by-different-concrete-class-in-hilt) – Andrew Jul 26 '21 at 21:04
  • No, it requires different qualifiers that you have to specify at compile-time. I need to switch dependencies at runtime. – Dmytro Karataiev Jul 27 '21 at 15:13
  • 2
    I actually already know the answer, but just didn't put it into a proper answer here. It is impossible with dagger hilt to provide different implementations of an interface at runtime for my particular use-case. – Dmytro Karataiev Jul 27 '21 at 15:14

2 Answers2

6

With Dagger it is impossible to replace dependencies at runtime in my use case.

During one of the Google Hilt sessions they recommended to use an if else statement in a Provides method: https://youtu.be/i27aNF-kYR4?t=3355 (that's what I prefer to avoid).

The answer above doesn't understand my question, because they are qualifying dependencies at compile time which I can't do. Since my fragment never knows the place it's used and I don't want to just duplicate code.

Here's an example where you can see exactly what I'm doing and that it can't be achieved with Hilt by design: https://github.com/dmytroKarataiev/daggerAndroidvsHiltRuntimeDependencies

Dmytro Karataiev
  • 1,214
  • 1
  • 14
  • 19
  • You can try using @Named("value") qualifiers for the different dependencies. Like in your fragment you can do something like @Named(VersionType) @Inject val analytics. and this VersionType will be a global variable whose value you can change from anywhere. only thing to take care here is to change the value of VersionType before you actually create the fragment – Pranay Mohapatra Mar 31 '22 at 11:14
  • You can't have variables in the @Named annotation, everything must be available at compile time. Dagger wouldn't be able to generate code with a dynamic variable. And also this approach even if it were possible, would be the same as if using a regular if/else statement somewhere, which I need to avoid. – Dmytro Karataiev Mar 31 '22 at 17:01
0

I think we could leverage Hilt's Qualifier feature to solve this multi binding issue. Here is some resources I found: https://developer.android.com/codelabs/android-hilt#8 I quote:

To tell Hilt how to provide different implementations (multiple bindings) of the same type, you can use qualifiers.

I think it's a way for Hilt to differentiate different implementations of the same interface.

To setup your Hilt module:

@Qualifier
annotation class PaidModule

@Qualifier
annotation class FreeModule

@InstallIn(ActivityComponent::class)
@Module
abstract class PaidActivityModule {

    @ActivityScoped
    @Binds
    @PaidModule
    abstract fun bindPaidModule(impl: PaidActivity): YourInterface
}

@InstallIn(ActivityComponent::class)
@Module
abstract class FreeActivityModule {

    @ActivityScoped
    @Binds
    @FreeModule
    abstract fun bindFreeModule(impl: FreeActivity): YourInterface
}

To inject:

@FreeModule
@Inject
lateinit var freeActivity: YourInterface

@PaidModule
@Inject
lateinit var paidActivity: YourInterface
Paul Wang
  • 157
  • 2
  • 3
  • That's not it, that's the same as the answer linked in the comments to my question. I need to be able to inject an interface without using a qualifier. Think of a Fragment that is used in 2 different activities, but it has to be injected with different implementations of the YourInterface. – Dmytro Karataiev Aug 24 '21 at 18:17
  • Oh well, sorry I couldn't provide an answer you were looking for. But with the given information, which is limited. I can only think of this answer. Unless you can add more information to your question. For starter, maybe what the interface looks like? Which Fragments? And where Fragments should be injected at? – Paul Wang Aug 24 '21 at 22:41
  • I believe the question has everything you need. I'll create a repo with an example to show you what I mean exactly. – Dmytro Karataiev Aug 25 '21 at 19:50
  • I'll add more details and a graph to this repo: https://github.com/dmytroKarataiev/daggerAndroidvsHiltRuntimeDependencies But I believe it's impossible to inject BlankFragment with different instances of Analytics with Dagger Hilt. – Dmytro Karataiev Aug 25 '21 at 21:08