google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.41k stars 2.01k forks source link

[Hilt]: Add support for app bundles (dynamic features) #3041

Closed sergeys-opera closed 2 years ago

sergeys-opera commented 2 years ago

There is Hilt, mentioned in the official Android documentation. There are app bundles which are required to be published since Aug 2021:

Important: From August 2021, new apps are required to publish with the Android App Bundle on Google Play.

There is a problem: Hilt doesn't work with ~app bundles~ dynamic features.

Of course you can publish your app as an app bundle with Hilt but as soon as you start splitting up the project by introducing dynamic features, Hilt breaks. And the reason of that is a single @HiltAndroidApp component which produces the code that depends on everything.

Ideally each dynamic feature should declare their own module component that is used only inside that module. Of course that would also require changing how dependencies are injected into activities and fragments as Application class wouldn't know about the module components (unless these is an API to let module components register in Application).

Q: is there a plan to make Hilt fully compatible with ~app bundles~ dynamic features on Android?

sergeys-opera commented 2 years ago

Updated the original comment as "app bundles" != "dynamic features"

Chang-Eric commented 2 years ago

Sorry for the slow response, but https://developer.android.com/training/dependency-injection/hilt-multi-module#dfm has the current guidance on how to use Hilt with dynamic features. There currently aren't any further plans in this area.

sergeys-opera commented 2 years ago

@Chang-Eric thanks for the response. The suggested "solution" is basically to switch to vanilla Dagger which is what we did at the end. But that is not what I would expect from a library which is included in Android Jetpack.

From the Android Jetpack's web page:

Jetpack is a suite of libraries to help developers follow best practices, reduce boilerplate code, and write code that works consistently across Android versions and devices so that developers can focus on the code they care about.

Moving from Hilt to Dagger in a semi-big project might be quite time-consuming and not always trivial (think, for example, about integration with ViewModel or WorkManager).

And it's very unfortunate that you learn about this only when you start using dynamic feature modules.

There currently aren't any further plans in this area.

Why not? It doesn't seem to be very difficult to fix. I explained in the first message how (adding a new annotation for feature modules similar to @HiltAndroidApp and making it possible to register dynamic components in the app).

Chang-Eric commented 2 years ago

You're right that we should mention this earlier in our documentation so people are aware. I'll edit the docs for that.

As for why there aren't further plans is that it is difficult. One of the big issues with the idea you presented is the fact that there will be multiple @Singleton components. This is a problem because of @Inject types that could be easily pulled into either root module component. Right now @Singleton is understood to be one instance per process, but that could easily be broken due to this. You might argue that Hilt/Dagger should know about these and try to dedupe, but imagine a case where you have two dynamic features using the same singleton that isn't in the base module. Which dynamic feature should own that singleton code? If one feature owned the code, then it would force the other feature to be downloaded just to use it. If shared somehow, that's coordination code Hilt/Dagger has to write without having any real knowledge of the dynamic feature boundaries. The real answer might be factoring that code into a third feature, but again, hard for Hilt/Dagger to coordinate all of that for you.

Anyway, that's just some insight into why this is a hard problem. I'm not saying we'll never do something in this area, or that we have zero ideas, but for practical purposes, you shouldn't assume we have any plans in this area.

sergeys-opera commented 2 years ago

Thanks for the answer.

One of the big issues with the idea you presented is the fact that there will be multiple @Singleton components. This is a problem because of @Inject types that could be easily pulled into either root module component. Right now @Singleton is understood to be one instance per process, but that could easily be broken due to this.

My understanding might be wrong but it looks to me this can be fixed by scoping all @Singletons in the app (annotated by @HiltAndroidApp). Of course feature module components shouldn't know about the app module (feature module can not depend on the app module) but this can be done dynamically - as soon as the feature module is loaded it can be registered in the app and all its @Singletons can be safely created. The code generated for a feature module would still reside in the module but it would crash if no app is registered prior to using it.

Anyway, that's just some insight into why this is a hard problem. I'm not saying we'll never do something in this area, or that we have zero ideas, but for practical purposes, you shouldn't assume we have any plans in this area.

:+1:

I guess the issue can be closed then.

Chang-Eric commented 2 years ago

It is kind of hard for Dagger to do something like that dynamically since Dagger is all based on static knowledge at compile time. What you are describing where you register with the app and load in the singletons dynamically would work if Dagger was based on using a map at runtime to hold scoped objects, but as written today the actual generated component effectively serves as the replacement for that map with the fields in the generated component acting in place of the map entries. This is what makes this non-trivial.