8

I'm using Dagger to inject dependencies into an Android application, and I stumbled on an issue which I am not entirely sure how to resolve in a clean way.

What I'm a trying to achieve is to instanciate helpers and inject them within my activity, and have these helpers contain injected members too.

What works

The activity where my helper is being injected:

public class MyActivity extends Activity {
    @Inject SampleHelper helper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ((MyApplication) getApplication()).inject(this);

        Log.i("debug", "helper = " + helper);
        Log.i("debug", "helper context = " + helper.context);
    }
}

The application creating the object graph:

public class MyApplication extends Application {
    private ObjectGraph graph;

    @Override
    public void onCreate() {
        super.onCreate();

        graph = ObjectGraph.create(getModules());
    }

    private Object[] getModules() {
        return new Object[] { new MyModule(this) };
    }

    public void inject(Object target) {
        graph.inject(target);
    }
}

The injection works perfectly when I instanciate directly a SampleHelper class, which in its turn receives an injected application context:

@Singleton
public class SampleHelper {

    @Inject public Context context;

    @Inject
    public SampleHelper() {}
}

With the following module:

@Module(
    injects = { MyActivity.class },
    complete = false,
    library = true
)
public class MyModule {
    private final MyApplication application;

    public MyModule(MyApplication application) {
      this.application = application;
    }

    @Provides @Singleton Context provideApplicationContext() {
        return application;
    }
}

What doesn't work

However, when I separate the helper interface from its implementation:

public interface SampleHelper {
}

@Singleton
public class SampleHelperImpl implements SampleHelper {

    @Inject public Context context;

    @Inject
    public SampleHelperImpl() {}
}

And add this to the dagger module:

public class MyModule {
    ...

    // added this method
    @Provides @Singleton public SampleHelper provideSampleHelper() {
        return new SampleHelperImpl();
    }

    ...
}

The context doesn't get injected in my SampleHelperImpl, as I would have expected. Now, I guess this is due to SampleHelperImpl being instanciated through direct constructor call rather that injection-initiated constructor call, because MyModule#provideApplicationContext() doesn't even get called, so my guess is I'm missing something about Dagger (which is likely, as my previous DI experiences only included Spring).

Any idea about how to have my context injected in my injected helper implementation, in a "clean Dagger" way?

Thanks a lot!

mrlem
  • 475
  • 4
  • 11
  • I'm also struggling with dagger right now, and have similar config to yours. I need to inject instances of activities into helpers, not application itself, but otherwise it's very similar. That's why I have a question to you: why do you inject context as a member into `SampleHelper`, not as constructor parameter? In my case, injection through constructor parameter fails, and therefore I'm wondering if there's something special about it, which should be avoided. I understand it's off topic, but if you can help, it would be appreciated. – Haspemulator Jul 24 '13 at 09:11
  • Actually, I now pass the context as a constructor parameter (as mentioned in my answer below), though it is not injected directly in the constructor itself, but rather as part of an @Provides method in the dagger module (and it works). My initial idea was to drop all code related to assigning my members thanks to dagger, but I could not get it to be as easy to use as, say spring-injection. That being said, I am aware they do not work in the same way and with the same constraints, and dagger still brings a lot. – mrlem Jul 24 '13 at 18:13
  • Hi, thanks for reply. Maybe you can take a look at my question? http://stackoverflow.com/questions/17839451/activity-graphs-and-non-found-dependency – Haspemulator Jul 24 '13 at 18:44

4 Answers4

18

This is a fairly old question but I think what you want is this:

@Provides @Singleton public SampleHelper provideSampleHelper(SampleHelperImpl impl) {
    return impl;
}

This way Dagger will create your SampleHelperImpl and therefore inject it.

alexanderblom
  • 8,632
  • 6
  • 34
  • 40
1

In case anyone is interested, when implementing an @Provides method in a dagger module, you can get dagger-handled objects instances like that:

@Provides @Singleton public SampleHelper provideSampleHelper(Context context) {
    SampleHelper helper = new SampleHelperImpl();
    helper.setContext(context);
    return helper;
}

This works, but I still feel it's a bit clumsy, as I have to call my helpers setters explicitly (typically what you want to get rid of with injection).

(I'll wait a bit in case someone comes with a better solution)

mrlem
  • 475
  • 4
  • 11
1

(Applies to Dagger v1.0.1)

Make sure you are using adapter injection. With reflective injection, dagger apparantly does no transitive injection on @Provides objects. I think this is a bug.

André W8
  • 21
  • 2
  • 1
    This sounds quite like my problem. But could you please just clarify 2 things: 1/ I thought it did not use reflection for its injections? (which is what I suppose you meant by "reflective injection", or did you mean annotation-based injection?) 2/ what do you mean by adapter injection: injections using @Provides in the dagger module? – mrlem Jul 24 '13 at 18:18
  • Dagger provides two ways of setting `@Inject` annotated fields. One is via reflection, the other is via a generated adapter class. In both cases the fields need to be at least package accessible. The reflection method is a fallback and might not be available in futur versions. To check what injection method you are using, set a breakpoint into your module's `@Provides` method, and examine your stack trace. Look out for: `injectMembers():118, ReflectiveAtInjectBinding {dagger.internal.plugins.reflect}` vs `injectMembers():82, YourClassname$$InjectAdapter {your.classes.package}` – André W8 Jul 31 '13 at 10:20
0

In terms of injecting the right context you might want to have a look at this sample https://github.com/square/dagger/tree/master/examples/android-activity-graphs.

jfrey
  • 707
  • 5
  • 8
  • Thanks for the reply: I already came across this example (there's not an awful lot of them ;). Indeed, in a real-life app, you need to inject the right context. But my initial question was a bit more general: separating Helper interface from implementation prevents @Inject from working within the implementation, as the helper instantiation is not handled directly by Dagger anymore. – mrlem Jul 17 '13 at 12:53