16

Using Dagger 2, I'm trying to inject a singleton object at multiple locations in a single scope. However, it seems my solution instead creates a new instance each time.

In this test project, I have a MainActivity which initializes the DaggerModule. The DaggerModule provides the objects Box and Cat, with Box taking Cat as a parameter. I also take in Cat in my MainActivity. Finally, I check the references of the both Cat variables injected (in the Box and in the MainActivity respectively), but they are not the same instance.

If I call provideCat() twice in my MainActivity instead, the same instance is provided.

MainActivity:

public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerModule daggerModule = new DaggerModule();
        DaggerComponent daggerComponent = Dagger_DaggerComponent.builder()
                .daggerModule(daggerModule).build();

        // Same Cat instance returned.
        Cat cat1 = daggerComponent.provideCat();
        Cat cat2 = daggerComponent.provideCat();
        Log.d("=== cat1: ", cat1.toString());
        Log.d("=== cat2: ", cat2.toString());

        // Different Cat instance returned. Why?
        Box box = daggerComponent.provideBox();
        Log.d("=== box cat: ", box.getCat().toString());
    }
}

@Module
public class DaggerModule {

    @Provides
    @Singleton
    public Cat provideCat() {
        return new Cat();
    }

    @Provides
    @Singleton
    public Box provideBox() {
        return new Box(provideCat());
    }

}

@Singleton
@Component(modules = { DaggerModule.class })
public interface DaggerComponent {

    Cat provideCat();

    Box provideBox();

}

public class Cat {

    @Inject
    public Cat() {
    }

}

public class Box {

    private Cat mCat;

    @Inject
    public Box(Cat cat) {
        mCat = cat;
    }

    public Cat getCat() {
        return mCat;
    }

}

Thanks in advance!

Edit: It works if provideBox takes in a Cat argument and uses that to create the Box, instead of calling provideCat directly from within provideBox.

    // Doesn't work, new Cat instance created.
    @Provides
    @Singleton
    public Box provideBox() {
        return new Box(provideCat());
    }

vs

    // Works, same singleton injected.
    @Provides
    @Singleton
    public Box provideBox(Cat cat) {
        return new Box(cat);
    }

What's the difference between calling provideCat in the MainActivity and doing it from within provideBox in the DaggerModule? Could it be that the Dagger compiler doesn't process the DaggerModule the same way it does with external classes and the annotations don't get applied if I call provideCat in there?

jomni
  • 759
  • 8
  • 21

1 Answers1

9

The reason I wanted to call provideCat from within provideBox was a misconception on my part of the Component interface. I misunderstood that the Component interface wasn't actually implemented by the Module, so the arguments of the Module's methods didn't have to be declared in the corresponding methods of the Component. Had that been the case, it would have forced me to create the Cat instance in the provideBox method call of the MainActivity, which I wanted to avoid (hence calling provideCat directly in the provideBox method of the Module). In fact, declaring arguments in the Component methods even made the Dagger compiler unable to compile.

But since the Component methods don't take arguments, the solution was simply to inject instances as arguments in the Module methods where needed (rather than calling the corresponding provide methods from within the Module itself), and only having to call the Component's parameterless methods from the MainActivity as follows:

MainActivity:

Cat cat = daggerComponent.provideCat();
Box box = daggerComponent.provideBox();

Component:

Cat provideCat();
Box provideBox(); <- no arguments

Module:

@Module
public class DaggerModule {

    @Provides
    @Singleton
    public Cat provideCat() {
        return new Cat();
    }

    @Provides
    @Singleton
    public Box provideBox(Cat cat) { <- arguments
        return new Box(cat);
    }

}

The Cat singleton instances of the MainActivity and Box are now the same and I didn't have to declare them from the MainActivity, but Dagger handled it all. Success! Still not sure why provide methods work differently when called from external classes than from within the Module itself, though.

jomni
  • 759
  • 8
  • 21
  • 2
    The reason that the behavior is different is that the instance management is handled by the component implementation, not the module. When you invoke the method directly, it is only going to call it like any other Java method, but when you let the component wire together the dependencies it will use a `dagger.internal.ScopedProvider` to ensure that there is only one instance. – gk5885 Apr 08 '15 at 22:50
  • 6
    Also, for types that are `@Inject`ed, you can just annotate the class (in this case `Cat` and `Box`) with `@Singleton` directly and avoid the `@Provides` methods altogether. – gk5885 Apr 08 '15 at 22:51
  • Had a very similar problem. This saved me a lot of head banging. Thanks! – AndroidDev Aug 25 '15 at 02:16
  • yeah. there was a bit of confusion for me as to the difference between calling a provder method and Injector method and why to use either. still not 100% on it. – filthy_wizard Sep 04 '15 at 12:03
  • Hello @gk5885 I don't understand well but this works for me. What is the difference between "@Provide @Singleton" and only providing an object which is a singleton ? Thanks – ClemM May 11 '17 at 09:39