Search code examples
dagger-2dagger

Dagger does not fo injection with void method


I have app wide component (contains only Singletons), which is accesible by static method App.getComponent();. My component contains method void inject(MainActivity activity) and it works fine. I also have void inject(TaskRepo repo), but this one does not work. In TaskRepoImpl() I invoke: App.getComponent().inject(this);, but it does not injects anything. I'm sure that I annotated public members with @Inject.

Injecting with methods like TaskRepo repo = App.getComponent().taskRepo(); works fine. So why does Dagger ignores these members?


Solution

  • In short, you need the method to be void inject(TaskRepoImpl impl); you can't just accept TaskRepo. Your case is described in the Component docs under "A note about covariance".

    Dagger is a compile-time framework: at compile time Dagger will take a look at your Component and write implementations based on the methods on your Component (and its Modules). Some of those methods might be members injection methods (same link as above), which are single-arg methods that set @Inject fields and call @Inject methods for the instance you pass in. These are typically void methods named inject, but they can be named anything, and they might also return the instance they inject (for chaining).

    The trick is, Dagger can only anticipate the fields and methods on the specific type you define, not its subclasses: if you create a members-injection method void inject(Foo foo), then only Foo's fields and methods count, even if Foo's subclass Bar has @Inject-annotated methods. Even if your Dagger graph knows about both Foo and Bar, it would not know about other Foo subclasses, and would not necessarily be prepared to inject them (because all of that happens at compile time). This is also not a problem for "provision methods" (zero-arg factories/getters on Components) because as long as Dagger knows how to create a particular concrete type for a binding, it can inject that concrete type and simply return the reference as its supertype or interface.

    Therefore, switching the injection to the actual implementation class avoids this problem, because Dagger can inspect the implementation class TaskRepoImpl at compile time and ensure it has the bindings that TaskRepoImpl defines in its @Inject-annotated methods and fields.

    @Component(modules = {...}) public interface YourComponent {
      /**
       * Only injects the fields and methods on TaskRepo, which might do
       * nothing--especially if TaskRepo is just an interface.
       */
      void inject(TaskRepo taskRepo);
    
      /**
       * Injects the fields and methods on TaskRepoImpl, TaskRepo, and
       * any other superclasses or interfaces.
       */
      void inject(TaskRepoImpl taskRepoImpl);
    
      /**
       * Gets a TaskRepo implementation. If you've bound TaskRepo to TaskRepoImpl,
       * Dagger _can_ inject all of TaskRepoImpl's fields, because it knows at
       * compile time that there's a TaskRepoImpl instance to inject.
       */
      TaskRepo taskRepo();
    }
    

    As a side note, constructor injection allows you to better-encapsulate your classes by using final fields and avoiding partially-constructed (constructed but not injected) instances. I recommend using constructor injection where possible, and recommend strongly against using field injection and constructor injection separately for the same type. (You're probably not doing this other than to debug your case, but I'm leaving the note here for future readers as well.)