Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dagger 2 singletons not working

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?

like image 646
jomni Avatar asked Apr 03 '15 15:04

jomni


People also ask

What does@ Inject do in Dagger?

You use the @Inject annotation to define a dependency. If you annotate a constructor with @Inject , Dagger 2 can also use an instance of this object to fulfill dependencies. This was done to avoid the definition of lots of @Provides methods for these objects.

How does Dagger 2 work?

Dagger automatically generates code that mimics the code you would otherwise have hand-written. Because the code is generated at compile time, it's traceable and more performant than other reflection-based solutions such as Guice. Note: Use Hilt for dependency injection on Android.

Why use Dagger 2 Android?

Dagger 2 walks through the dependency graph and generates code that is both easy to understand and trace, while also saving you from writing the large amount of boilerplate code you would normally need to write by hand to obtain references and pass them to other objects as dependencies.

What is Dagger 2 Android?

Dagger 2 is a compile-time android dependency injection framework that uses Java Specification Request 330 and Annotations. Some of the basic annotations that are used in dagger 2 are: @Module This annotation is used over the class which is used to construct objects and provide the dependencies.


1 Answers

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.

like image 152
jomni Avatar answered Sep 18 '22 06:09

jomni