27

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?

Dalbergia
  • 1,699
  • 1
  • 17
  • 26

1 Answers1

25

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).

Oliver Spryn
  • 16,871
  • 33
  • 101
  • 195
Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144
  • 1
    Thank you so much for this answer! I've followed your suggestions, and everything is working well. – Dalbergia Jul 11 '17 at 19:50
  • 1
    Is it working anyone? Followed the same process but not working. I am getting “cannot find symbol class DaggerYourComponent” error while building the app. @Dalbergia, Is it working without UI component of android? – Tejas Mehta Jul 17 '17 at 14:40
  • 2
    It does work for me and apparently the OP also made it work. @TejasMehta try clean/rebuild or something. Your error means your component wasn’t properly generated or you’re not using the correct name. – Martin Marconcini Jul 17 '17 at 20:08
  • 1
    Thanks @MartinMarconcini After multiple clean/rebuild solved the issue. Its working now. – Tejas Mehta Jul 18 '17 at 04:35
  • @TejasMehta, yes, MartinMarconcini 's answer was correct, as you found. "DaggerYourComponent" is an implementation of the "YourComponent" interface, generated by Dagger as part of the build process, so you have to force a build to happen before the class willo be recognized. – Dalbergia Jul 18 '17 at 15:54
  • How does this work with DaggerAppCompatActivity? I get errors surrounding the injectors. – masterwok Jun 18 '18 at 22:32
  • How to achieve this in case I have created a component and my library uses different modules..? – Himanshu May 28 '20 at 07:11
  • @Himanshu I don't fully understand what you mean by _uses different modules_, Dagger is a complicated program, it can do a lot, and it can be very confusing; I suggest you post a concrete question to get a better answer. – Martin Marconcini May 28 '20 at 10:09
  • @VikashParajuli Sure, [here's the official Google Documentation](https://developer.android.com/training/dependency-injection/hilt-android) about using Android/Hilt. Very easy to follow and you should have it up and running in 10 minutes. – Martin Marconcini May 15 '21 at 10:00
  • @MartinMarconcini My typing mistake I want to create an android library without Activity and Fragment. Can you please help me with that? – Viks May 16 '21 at 01:15
  • @VikashParajuli I haven't had the need to do or try this. So I cannot help you with that. You can, however [search and find other people](https://github.com/google/dagger/issues/2132) trying to do the same. Perhaps you will find your answer there. – Martin Marconcini May 16 '21 at 13:24