Injecting application context in library module wi

2020-06-17 04:58发布

问题:

I'm building an app with some features: a ContentProvider a SyncAdapter, a Job service and related persistence logic. On top on these there are the Activities with the UI. I'm trying to put all said features in a separate library module, because in theory their logic is stand-alone and would be reusable by any application.

Now comes Dagger2. The first node (main Component) of my library's dependency graph does need to provide Context, and this Context has to be injected from the Application, because the library scope has the same lifecycle of the application. To be self contained, obviously, my library should not directly use my Application class.

These are the possibilities I thought of:

  • Build the library's main Component in my Application and store it in a global static class/enum as suggested here but I'm concerned that using such a static reference could be an anti-pattern.
  • Pack in the library an Application class which builds the library scoped Component, cast app context to this class in the library to use the component and then extend this Application class on the main app. This works, but if there's more than one library it's not viable anymore.
  • Use the factory pattern: put provision methods in the library component that provide the factory which in turn is given the locally available context as a parameter. (As explained here). This seems viable, although it adds extra complexity.
  • Last but non the least, give up trying to modularize the components, since being dependent on the application context breaks the concept of modularity.

What is the correct way to do this?

回答1:

Dagger 2 for Android comes to the rescue. It provides the concept of AndroidInjector, which is a Component that can be used to inject an instance in a static way, without having to know the dependency provider. Moreover, using the Dagger- prefixed classes provided out of the box, the injected dependencies look like coming from nowhere. Awesome.

All you have to do is declare in the library a top-level Module which is installed in the Application Component. This Module will provide all the dependencies and the SubComponents needed by the library, which will automatically inherit the @AppContext Context that you seeded in the dependency graph, ready to be injected anywhere in you library, as well as every dependency you provide through the main Application Component.

Here's a short example (written in Kotlin):

@Component(modules = [
    AndroidSupportInjectionModule::class,
    AppModule::class,
    LibraryModule::class //plug-in the library to the dependency graph
])
@Singleton
interface AppComponent : AndroidInjector<App> {

    @Component.Builder
    abstract class Builder : AndroidInjector.Builder<App>() {

        @BindsInstance
        abstract fun appContext(@AppContext context: Context)

        override fun seedInstance(instance: App) {
            appContext(instance)
        }
    }
}

Edit: extended examples

An example of the Application subclass:

// DaggerApplication provides out-of-the-box support to all the AndroidInjectors.
// See the class' code to understand the magic.
public class App extends DaggerApplication {

@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
    // We only provide its own Injector, the Application Injector,
    // that is the previous AppComponent
    return DaggerAppComponent.builder().create(this);
}

And in your Android library:

@Module
public abstract class LibraryModule {

    @ContributesAndroidInjector
    public abstract LibraryActivity contributeLibraryActivityInjector();

}

public class LibraryActivity extends DaggerAppCompatActivity {

    @Inject
    @AppContext
    Context appContext;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceSate);
        // here you automagically have your injected application context!
        ExternalSingleton.getInstance(appContext)
    }
}