Dagger: Hilt: Question, How to reuse a module and provide its bindings

Created on 16 Sep 2020  路  7Comments  路  Source: google/dagger

We are trying out Hilt for our production project, we have a use case that I'm not sure how Hilt would handle it
This is our approach towards providing different bindidngs when resuing a module:

Note I simplified the code below our actual use case has lots of modules and using Qulifiers is not convenient

@Module
class WidgetListModule {

    @Provides
    @FragmentScope
    fun provideCategoryViewModelFactory(
        widgetListDataSource: WidgetListDataSource<*>, // Not provided anywhere, user should provide it
        compositeDisposable: CompositeDisposable,
        threads: Threads,
        alak: Alak,
    ): ViewModelProvider.Factory {
        return viewModelFactory {
            WidgetListViewModel(
                compositeDisposable = compositeDisposable,
                dataSource = widgetListDataSource,
                threads = threads,
                alak = alak,
            )
        }
    }
}

The module that includes this module

@Module(includes = [WidgetListModule::class])
class ManageModule {

    @Provides
    @FragmentScope
    fun provideWidgetListDataSource(
        api: ManagePostAPI
    ): WidgetListDataSource<*> {
        return WidgetListGetDataSource(api::getPage)
    }
}
@FragmentScope
@Subcomponent(modules = [ManageModule::class])
interface ManageFragmentInjector {
    fun inject(manageFragment: ManageFragment)
}

And we have cases that the injector being used to inject a field is determined at run time like this:

override fun inject() {
        if (args.source == "foo") {
            mainActivityComponent.fooFragmentInjector()
                .inject(this)
        } else {
            mainActivityComponent.barFragmentInjector()
                .inject(this)
        }
    }

So my question is how can I resuse a module and provide its dependencies with hilt?

All 7 comments

Hi @MRezaNasirloo,

IIUC, it looks like you have two separate subcomponents that can be used to inject that same fragment depending on some runtime argument passed to the fragment, is that right?

That approach won't work well with Hilt since all fragments using @AndroidEntryPoint to do injection share the same component. However, you could try to configure this at the individual binding level. For example, each @Provides that needs to be configured can inject the fragment and return a different implementation based on the fragment's argument.

Hopefully that gives you some ideas.

Hi @bcorso
No, my main question is how do I reuse a module and provide its bindings, ignore the last code segment that I shared, that's another problem.

Actually, I didn't fully understand your solution can you elaborate more or maybe share a pseudo code?

Sorry, this fell through the cracks.

No, my main question is how do I reuse a module and provide its bindings

Sorry, I'm not sure I understand what you mean by "reuse a module and provide its bindings" but I'll take a shot.

Do you mean that you have multiple fragments, say FooFragment and BarFragment, that both want all of the bindings provided by WidgetListModule but need to provide their own implementation of WidgetListDataSource<*>?

Sorry, this fell through the cracks.

No, my main question is how do I reuse a module and provide its bindings

Sorry, I'm not sure I understand what you mean by "reuse a module and provide its bindings" but I'll take a shot.

Do you mean that you have multiple fragments, say FooFragment and BarFragment, that both want all of the bindings provided by WidgetListModule but need to provide their own implementation of WidgetListDataSource<*>?

Exactly

Yeah, this is a case where it's possible to do with Hilt but you will loose a bit of the compile-time guarantees for that binding.

One solution is to provide the implementation from each fragment via an interface and have the @Provides method delegate to the fragment (there's a similar use case in the migration guide, here). For example:

// Implemented by Fragments that provide a custom WidgetListdataSource.
interface HasWidgetListdataSource {
  WidgetListDataSource getWidgetListDataSource();
}

// Provides the WidgetListDataSource for a specific fragment or null if the fragment doesn't provide it
@Module
@InstallIn(FragmentComponent.class)
interface WidgetListdataSourceModule {
  @Provides
  @Nullable
  WidgetListDataSource provideWidgetListDataSource(Fragment fragment) {
    return (fragment instanceof HasWidgetListdataSource)
        ? ((HasWidgetListdataSource) fragment).getWidgetListDataSource();
        : null; // or DefaultWidgetListDataSource
  }
}

// Provides FooWidgetListdataSource to any dependency requesting WidgetListdataSource
class FooFragment extends HasWidgetListdataSource {
  @Inject FooWidgetListdataSource fooWidgetListdataSource;

  @Override
  WidgetListDataSource getWidgetListDataSource() {
    return fooWidgetListdataSource;
  }
}

// Provides BarWidgetListdataSource to any dependency requesting WidgetListdataSource
class BarFragment extends HasWidgetListdataSource {
  @Inject BarWidgetListdataSource barWidgetListdataSource;

  @Override
  WidgetListDataSource getWidgetListDataSource() {
    return barWidgetListdataSource;
  }
}

Closing this. Feel free to reopen if you still have questions

For anyone coming to this issue, the solution provided by @bcorso works with a bit of adjustment, since the injection has not happened yet, you will get an NPE exception if you access the fooWidgetListdataSource so you can't have @Inject annotation on it like @Inject FooWidgetListdataSource fooWidgetListdataSource; instead, you can access your dependence with an entry point interface

override val widgetListRepository: WidgetListRepository by lazy {
        EntryPoints.get(this, ManageEntryPoint::class.java).manageRepository()
    }

    @EntryPoint
    @InstallIn(FragmentComponent::class)
    interface ManageEntryPoint {

        @Named(MANAGE)
        fun manageRepository(): WidgetListRepository
    }
Was this page helpful?
0 / 5 - 0 ratings