How can I build a Dagger-based Android library wit

2019-03-12 19:42发布

问题:

I'm working on an Android library that is basically a client for some REST services I've written. I have several storage classes, network queues, parsers, and so on, and like many such classes, they have dependencies on Context or on things like SharedPreferences that are constructed from Context. These objects are all hidden behind a facade class, so consumers of my library don't see them or interact with them directly.

For my own sanity, I would like to use Dagger 2 for dependency injection to manage instances of these classes INTERNALLY within my library. However, I don't want to force apps using my library to use Dagger themselves; just because I chose to use Dagger doesn't mean my users should have to.

All the tutorials I've seen seem to expect that I'm building an application, not just a library. Many of these tutorials tell me I should make my Application class inherit from DaggerApplication. In my case, though, I don't have an Application (or any Activity or Service` classes) in my library at all, and I don't want my users to have to use Dagger's base classes.

So how can I use Dagger without "leaking" it out of my library? I've found a partial answer here, but I'm not sure how adapt the author's "wrapper" pattern to handle my dependency on Context. Can I just pass a context into the wrapper's getComponent() method, or will Dagger be able to obtain a Context reference some other way?

回答1:

A library is almost like an Application (when it comes to Dagger). Yes, you don’t have an application object, but you don’t really need one.

As a consumer of your Library, I expect it to be simple to use, so I don’t want to know what dagger is at all (or if you internally use it).

Let your users pass a Context when they call your library for the first time (for example). Have a DaggerInjector (I think your sample calls it wrapper) that has a static reference to your Component interface.

Example (and as such, just a generic example):

public class DaggerInjector {

    private static YourComponent component;

    private DaggerInjector() {
        super();
    }

    public static YourComponent getComponent() {
        return component;
    }

    public static YourComponent buildComponent(Context context) {
        component = DaggerYourComponent
                .builder()
                .yourModule(new YourModule(context))
                .build();
        return component;
    }
}

Your “module” can look like:

@Module
public class YourModule {

    private Context context;

    public YourModule(Context context) {
        this.context = context;
    }

    @Provides
    @Singleton
    final Context providesContext() {
        return context;
    }
}

To use it:

Have your users call a method (or you call it yourself the first time if the component is null):

DaggerInjector.buildComponent(context);

This will ensure the Dagger component is initialized and the code is generated. Understand that calling buildComponent is an expensive task (Dagger has to do a lot!) so only do it once (unless you need to re-initialize the library with different values known at runtime only).

Some libraries simply ask for a context in every call, so that is not out of the question; you could, then, initialize dagger the first time you're called (by checking if getComponent() is null in the injector).

After your DaggerInjector.getComponent() is not null anymore, you can now add @Inject and the appropriate "injectable" stuff…

e.g.: in YourModule you could have:

@Provides
SomeObject providesSomeObject() {
    return new SomeObject();
}

// THIS “Context” here is automatically injected by Dagger thanks to the above.
@Provides
@Singleton
SomeOtherObject providesSomeOtherObject(Context context) {
    return new SomeOtherObject(context); //assume this one needs it
}

and in any "injectable" object (that is, an object that has an inject method in your component…) you can do:

public class AnObjectThatWantsToInjectStuff {

    @Inject
    SomeObject someObject;
    @Inject 
    SomeOtherObject someOtherObject;

    public AnObjectThatWantsToInjectStuff() {
          super();
          DaggerInjector.getComponent().inject(this);

          // you can now use someObject and someOtherObject
    }
}

For the above to work, you need in YourComponent (which is an Interface) code like this:

void inject(AnObjectThatWantsToInjectStuff object);

(otherwise calling DaggerInjector.getComponent().inject(this) will fail at compile time)

Notice I never passed a context to YourInjectableContext, Dagger already knows how to obtain it.

Be careful with leaks tho. I recommend that you store context.getApplicationContext() instead of just plain Context for all/most cases (unless you explicitly need an Activity context for inflating layouts/theme purposes, the application context supplied by the consuming application is all you need).