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

Make library with Hilt and the client app without Hilt #4420

Open betaHi opened 2 weeks ago

betaHi commented 2 weeks ago

Hi, I met a urgent issue and could not resolve:

Background : 1: We build a aar uses the Hilt framework, and if we don't use Hilt, need to rewrite the entire project, which is almost impossible since it's a large project and released for a long time. 2: Due to the special nature of the project, the client company cannot use Hilt in their project.

But Hilt requires annotations in the Application with @HiltAndroidApp, and it contradicts the client app that cannot use Hilt.

Problem: Is there any solution that we make library with Hilt and the client company doesn't need to use 'hilt' in their app project? Thanks.

kuanyingchou commented 2 weeks ago

Probably not. Please see https://github.com/google/dagger/issues/2132.

betaHi commented 2 weeks ago

Thanks , is there any way, such as using tools to compile the Application manually? I want lib to provide a CustomApplication, and add @HiltAndroidApp annotation, and let the client company inherit this compiled CustomApplication, and I think it could avoid the problem of not being able to use hilt.

But I have tried it, and if the plugin of gradle is not "com.android.application", it will not compile, that is @HiltAndroidApp cannot be used in lib.

Probably not. Please see #2132.

yoobi commented 2 weeks ago

Will we be able to do that with hilt at some point ? Or Are we forced to go back using vanilla dagger ?

chris-harris-sncr commented 1 week ago

I have recently been experimenting with adding support to Hilt for use internally in libraries (and also in internal applications). This seems like something plenty of people in the Android community would be very interested to have, but would there be interest from Google in adding such optional functionality (that doesn't impact behavior for regular Hilt usage that assumes application must use Hilt too)?

yoobi commented 1 week ago

It would promote modularization which is in Google best practices so yeah there is an interest Even though here we are talking about creating SDK it does often come with modularization

chris-harris-sncr commented 1 week ago

To give a brief idea of how I have it working, the library (and any other Hilt libraries it depends on transitively) need to have their configuration updated to include something like:

hilt {
    applicationOverride = "com.example.mylibrary.LibraryApplication"
}

and the top-level library needs to include a @HiltAndroidApp class com.example.mylibrary.LibraryApplication that extends android.app.Application and has a static method: public static Object getGeneratedComponent()

Note: the implementation does not use reflection.

Admittedly it's weird to use android.app.Application in a library, but it was by far the easiest way to get it up and running. Perhaps the processor could be updated to allow @HiltAndroidApp on classes that don't extend Application, or a separate @HiltAndroidLibrary could be added.

Chang-Eric commented 1 week ago

This has been something we've thought about for a while (even since the beginning of Hilt) and we've set ourselves up for a possible future where we support this (you'll notice some things are under dagger.hilt and some are under dagger.hilt.android), but so far it isn't something we've been able to prioritize for various reasons. There is an alternative out there though in Anvil that seems to be fairly popular.

There's also some extra complexity when you consider Android SDK support like SDK fragments or activities that now have to know to get the Dagger component from the static method instead of from the application, so depending on what you are looking for it can also be more or less complicated.

This isn't really a satisfying answer, I know, but I guess this is just saying we're aware of this, and we're not philosophically opposed to the idea, but we also sadly lack the time needed to really make it a reality.

chris-harris-sncr commented 1 week ago

Thanks for the quick reply @Chang-Eric!

I wasn't asking for Google to provide the resources to implement this. I do already have working code that handles the cases you mentioned for all the Android components (for production code so far - I haven't looked at testing yet). My question is would it be worth my time to continue working on this and to create a pull request for it or not?

Chang-Eric commented 1 week ago

My question is would it be worth my time to continue working on this and to create a pull request for it or not?

I really appreciate it, but unfortunately, I think probably not.

There are a number of issues that make this topic a bit complex. One as you mentioned is the weird usage of the Application which probably wouldn't work for us. Another is something more of a problem for code bases with shared Hilt libraries (more of a Google internal issue), but how do you get those shared libraries with SingletonComponent entry points to use the right Dagger root and not try to get it from the application context in the right situations. Shared libraries also bring up the question of how this could technically break the definition of @Singleton (as there could be multiple SDK roots), which means you might want to generate a different scoping annotation. These aren't necessarily all big problems, but more just saying that supporting this will likely go beyond the initial changes and so we would need to be ready to deal with the expanded scope.

chris-harris-sncr commented 1 week ago

Yes I agree that it would be much better if these standalone libraries didn't use Application class - I just haven't gotten to look at that yet.

So Hilt libraries would need to be built either to work with any Hilt application (without applicationOverride config), or with a specific Hilt root library (with an applicationOverride config). I imagine in Google's case for internal shared Hilt libraries you would probably still just build your libraries to work with any Hilt application.

But if you ever needed any of those Hilt libraries to be consumed externally by a non-Hilt application (it doesn't sound like you do!), all you have to do is wrap them with another external library that contains @HiltAndroidApp(or @HiltAndroidLibrary), and then your internal applications can use the internal library (with Application class updated to match the expected applicationOverride name and with a static method added) and external non-Hilt applications can use the API that hides Hilt.

Chang-Eric commented 1 week ago

The issue is that right now we pull in modules and entry points via deps, so even if your library is configured to work with the applicationOverride, if you include that into a Hilt application under the assumption that it won't interact, the modules and entry points will still be discovered by the Hilt application.

chris-harris-sncr commented 1 week ago

Ah yes - I hadn't considered that case.

I suppose to avoid that an extra field would need to be added to @InstallIn in the library to indicate they shouldn't be included by a Hilt application e.g. @InstallIn(value = SingletonComponent.class, applicationOverride = true) where the default value would be false. Or it could even include the full applicationOverride value to allow multiple applicationOverrides in same class path e.g. @InstallIn(value = SingletonComponent.class, applicationOverride = "com.example.mylibrary.LibraryApplication") where the default value is null/empty string. It could added by the Hilt gradle plugin as part of byte code transformation.