10

I'm using Dagger for dependency injection in an Android project, and can compile and build the app fine. The object graph appears to be correct and working, but when I add dagger-compiler as a dependency to get errors at compile time, it reports some bizarre errors:

[ERROR] error: No binding for com.squareup.tape.TaskQueue<com.atami \
    .mgodroid.io.NodeIndexTask> required by com.atami \
    .mgodroid.ui.NodeIndexListFragment for com.atami.mgodroid \
    .modules.OttoModule
[ERROR] error: No binding for com.squareup.tape.TaskQueue<com.atami \
    .mgodroid.io.NodeTask> required by com.atami \
    .mgodroid.ui.NodeFragment for com.atami.mgodroid.modules.OttoModule
[ERROR] error: No injectable members on com.squareup.otto.Bus. Do you want 
     to add an injectable constructor? required by com.atami. \
     mgodroid.io.NodeIndexTaskService for 
     com.atami.mgodroid.modules.TaskQueueModule

The Otto error looks like the one Eric Burke mentions in his Android App Anatomy presentation about not having a @Provides annotation, but as you can see below I do.

My Otto and TaskQueue modules are as follows:

@Module(
        entryPoints = {
                MGoBlogActivity.class,
                NodeIndexListFragment.class,
                NodeFragment.class,
                NodeActivity.class,
                NodeCommentFragment.class,
                NodeIndexTaskService.class,
                NodeTaskService.class
        }
)
public class OttoModule {

    @Provides
    @Singleton
    Bus provideBus() {
        return new AsyncBus();
    }

    /**
     * Otto EventBus that posts all events on the Android main thread
     */
    private class AsyncBus extends Bus {
        private final Handler mainThread = new Handler(Looper.getMainLooper());

        @Override
        public void post(final Object event) {
            mainThread.post(new Runnable() {
                @Override
                public void run() {
                    AsyncBus.super.post(event);
                }
            });
        }
    }
}

...

@Module(
    entryPoints = {
        NodeIndexListFragment.class,
        NodeFragment.class,
        NodeIndexTaskService.class,
        NodeTaskService.class
    }
)
public class TaskQueueModule {

    private final Context appContext;

    public TaskQueueModule(Context appContext) {
        this.appContext = appContext;
    }

    public static class IOTaskInjector<T extends Task> 
        implements TaskInjector<T> {

        Context context;

        /**
         * Injects Dagger dependencies into Tasks added to TaskQueues
         *
         * @param context the application Context
         */
        public IOTaskInjector(Context context) {
            this.context = context;
        }

        @Override
        public void injectMembers(T task) {
            ((MGoBlogApplication) context.getApplicationContext())
                .objectGraph().inject(task);
        }
    }

    public static class ServiceStarter<T extends Task> 
        implements ObjectQueue.Listener<T> {

        Context context;
        Class<? extends Service> service;

        /**
         * Starts the provided service when a Task is added to the queue
         *
         * @param context the application Context
         * @param service the Service to start
         */
        public ServiceStarter(Context context, 
                              Class<? extends Service> service) {
            this.context = context;
            this.service = service;
        }

        @Override
        public void onAdd(ObjectQueue<T> queue, T entry) {
            context.startService(new Intent(context, service));

        }

        @Override
        public void onRemove(ObjectQueue<T> queue) {
        }
    }


    @Provides
    @Singleton
    TaskQueue<NodeIndexTask> provideNodeIndexTaskQueue() {
        ObjectQueue<NodeIndexTask> delegate = 
            new InMemoryObjectQueue<NodeIndexTask>();
        TaskQueue<NodeIndexTask> queue = new TaskQueue<NodeIndexTask>(
            delegate, new IOTaskInjector<NodeIndexTask>(appContext));
        queue.setListener(new ServiceStarter<NodeIndexTask>(
            appContext, NodeIndexTaskService.class));
        return queue;
    }

    @Provides
    @Singleton
    TaskQueue<NodeTask> provideNodeTaskQueue() {
        ObjectQueue<NodeTask> delegate = 
            new InMemoryObjectQueue<NodeTask>();
        TaskQueue<NodeTask> queue = new TaskQueue<NodeTask>(
            delegate, new IOTaskInjector<NodeTask>(appContext));
        queue.setListener(new ServiceStarter<NodeTask>(
            appContext, NodeTaskService.class));
        return queue;
    }
}

...

/**
 * Module that includes all of the app's modules. Used by Dagger
 * for compile time validation of injections and modules.
 */
@Module(
        includes = {
                MGoBlogAPIModule.class,
                OttoModule.class,
                TaskQueueModule.class
        }
)
public class MGoBlogAppModule {
}
JJD
  • 50,076
  • 60
  • 203
  • 339
SeanPONeil
  • 3,901
  • 4
  • 29
  • 42

1 Answers1

20

Dagger's full graph analysis works from a complete module. i.e. @Module(complete = true), which is the default. Because it's the default, dagger will, by default, assume that all bindings are available from that module or those modules it includes explicitly.

In this case, you've given two modules that you claim are complete, but Dagger has no way to tie these together at compile time without an additional signal. In short, without OttoModule knowing about TaskQueueModule, the compiler will attempt to analyse OttoModule for its claimed completeness, and fail, because it doesn't now about TaskQueueModule.

Modify OttoModule's annotation as such:

@Module(
  includes = TaskQueueModule.class,
  entryPoints = {
    MGoBlogActivity.class,
    NodeFragment.class,
    NodeActivity.class,
    NodeCommentFragment.class,
    NodeIndexTaskService.class,
    NodeTaskService.class
  }
)

and then Dagger will know that for OttoModule to be complete, it includes the other module as part of its full definition.

Note:dagger-compiler can't detect that TaskQueueModule is there on the class path and just "know" that the developer intended it to be used with OttoModule without that additional signal. For instance, you might have several modules which define task queues and which one would it select? The declaration must be explicit.

Nathan Taylor
  • 879
  • 1
  • 9
  • 16
Christian Gruber
  • 4,691
  • 1
  • 28
  • 28
  • What defines a module as "complete"? Right now I have a Bus and a TaskQueue being injected into the same class, but a Bus isn't being injected into a TaskQueue. The way I see it, the TaskQueue isn't being used with OttoModule directly. – SeanPONeil Feb 11 '13 at 21:37
  • Maybe a better question is should a module contain all of the dependencies for an object in order to be complete? Or should a module contain one type of dependency, then multiple modules provide dependencies towards the same set of objects, like the way I have it above? – SeanPONeil Feb 11 '13 at 21:50
  • 2
    A module is complete if all the bindings needed to satisfy the needs of its entry points can be found, either via @Provides methods in itself, @Provides methods in explicitly `included` modules, or in discoverable injectable classes (classes with @Inject fields or constructor). – Christian Gruber Feb 15 '13 at 16:19
  • That's what was confusing me, I didn't realize the module had to satisfy every binding in it's entry points. I was under the impression that if every binding was resolved after the object graph was created, the modules would be "complete", even though that makes no sense now that I type that out. Thanks for your help clarifying! – SeanPONeil Feb 15 '13 at 16:32