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?
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
FooFragmentandBarFragment, that both want all of the bindings provided byWidgetListModulebut need to provide their own implementation ofWidgetListDataSource<*>?
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
}