Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dagger: Inject field on provided pojo

Currently testing with dagger, what I want to do is instantiate and inject different Bar implementations. How can I inject fields in provided fields? for example:

Module:

@Module(
        injects = {
                Main.class
        },
        complete = false,
        library = true
)
public class ExampleTestModule {
    @Provides
    public Foo providesFoo() {
        return new Foo();
    }
    @Provides
    public Bar providesBar(BarImpl impl) {
        // return new BarImpl(); // null
        return impl;
    }
}

Main:

public class Main {
    @Inject
    Foo foo;
}

Foo:

public class Foo {
    @Inject
    Bar bar;
}

Bar:

public interface Bar {

}

BarImpl

public class BarImpl implements Bar {
}

TestCase:

public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }


    public void testFoo() {
        Main main = new Main();
        ObjectGraph.create(new ExampleTestModule()).inject(main);
        assertNotNull(main.foo);
    }

    public void testFooBar() {
        Main main = new Main();
        ObjectGraph.create(new ExampleTestModule()).inject(main);
        assertNotNull(main.foo.bar);
    }
}

Main.Foo is not null but Main.Foo.Bar is null.

like image 789
Cypher Text Avatar asked Jan 09 '23 12:01

Cypher Text


1 Answers

You are never injecting bar into foo.

ObjectGraph.create(new ExampleTestModule()).inject(main);

This line will only look at the fields of main which are annotated by @Inject, and inject them. There is no recursive behavior.


Fixing the problem

Let's go step-by-step:

  • You provided complete = false and library = true in your Module. You should only use these if really necessary. Dagger will give you warnings when something is wrong, and these properties surpress these warnings. For example, removing them raises the following warning when compiling:

    Error:(11, 8) error: No injectable members on BarImpl. Do you want to add an injectable constructor? required by providesBar(BarImpl) for ExampleTestModule.
    
  • Let's add an empty injectable constructor to BarImpl, as it suggests:

    public class BarImpl implements Bar {
        @Inject
        BarImpl(){
        }
    }
    
  • Compiling will give a new error:

    Error:(11, 8) error: Graph validation failed: You have these unused @Provider methods:
    1. ExampleTestModule.providesBar()
    Set library=true in your module to disable this check.
    
  • Apparently, providesBar() is never used. That means, the bar field in Foo will never be injected. You can do two things:

    1. Inject bar manually:

      ObjectGraph graph = ObjectGraph.create(new ExampleTestModule());
      graph.inject(main);
      graph.inject(main.foo);
      
    2. Use injectable constructors (Preferred option):

      public class Foo {
          Bar bar;
      
          @Inject
          Foo(Bar bar){
              this.bar = bar;
          }
      }
      
  • Using the injectable constructor, you will now have a compile error in providesFoo(), since you don't supply a Bar instance in the Foo constructor. The nice thing about Dagger is, you can safely completely remove this method. Since Foo is annotated with @Injectable, everywhere it needs to inject a Foo instance, it uses this constructor. And when it uses this constructor, it notices it needs a Bar instance, and injects this as well.

  • Finally, we can remove the @Inject annotation from the Foo field in Main, and create an injectable constructor. Using ObjectGraph.get(Class<?>) we can retrieve a fully instantiated Main instance.


The result

The end result should look like this:

Module:

@Module(
        injects = Main.class
)
public class ExampleTestModule {
    @Provides
    public Bar providesBar(BarImpl impl) {
        return impl;
    }
}

Main:

public class Main {
    Foo foo;

    @Inject
    Main(Foo foo) {
        this.foo = foo;
    }
}

Foo:

public class Foo {
    Bar bar;

    @Inject
    Foo(Bar bar){
        this.bar = bar;
    }
}

Bar:

public interface Bar {
}

BarImpl:

public class BarImpl implements Bar {
    @Inject
    BarImpl(){
    }
}

ApplicationTest:

public class ApplicationTest extends ApplicationTestCase<Application> {

    public ApplicationTest() {
        super(Application.class);
    }


    public void testFoo() {
        Main main = ObjectGraph.create(new ExampleTestModule()).get(Main.class);
        assertNotNull(main.foo);
    }

    public void testFooBar() {
        Main main = ObjectGraph.create(new ExampleTestModule()).get(Main.class);
        assertNotNull(main.foo.bar);
    }
}

Conclusion

From the result, we can conclude some things:

  • Don't just add library = true and complete = false to your module. This should only be necessary when using multiple complex modules.
  • Try to use injectable constructors. This is what Dagger's built for, and works best. An extra perk is that you can now have your fields private, like they should be.
  • When using injectable constructors, you really only need to create providesXXX methods when injecting instances of interfaces, like we did with Bar and BarImpl. Because, hey, that's exactly what Dependency Injection is for, right?
like image 198
nhaarman Avatar answered Jan 19 '23 01:01

nhaarman