Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting the same object with different interfaces using Dagger

Tags:

dagger-2

Let's say I have a singleton created by Dagger 2 which implements the interfaces Foo and Bar. There are places in the code which use Foo, and other places that use Bar, but whichever interface is injected I want the same singleton. Is this possible?

like image 748
Erik Browne Avatar asked Sep 29 '17 21:09

Erik Browne


2 Answers

Yes, this is easy to do, but it's also easy to do incorrectly. The trick is that you need to be careful which you make @Singleton: Foo, Bar, or FooBarImpl.

In Dagger, any particular binding can be marked Singleton, and that's where Dagger interferes to say "let me cache this instance". Interfaces like Foo and Bar will have it marked on the @Binds or @Provides method, and concrete classes like FooBarImpl will have it marked on a @Provides method or on the actual class that has an @Inject annotation.

Ideal: Mark your implementation @Singleton

@Singleton public class FooBarImpl implements Foo, Bar { /* ... */ }
// in your module:
@Binds Foo bindFoo(FooBarImpl impl);
@Binds Bar bindBar(FooBarImpl impl);

This way, whether a consumer asks for a Foo, Bar, or FooBarImpl, they'll always go to Dagger's internal double-check-locked caching instance ("DoubleCheck"). Foo and Bar can be marked @Singleton as well, but it's a little wasteful, because the internal FooBarImpl provider will always return the same instance anyway. There's no need to keep a separate copy on the Foo Provider or the Bar Provider.

If FooBarImpl is external or otherwise outside of your control, you can mark @Singleton on the @Provides method instead.

Less ideal: Mark one and bind the other to it

/* not Singleton */ public class FooBarImpl implements Foo, Bar { /* ... */ }
// in your module:
@Binds @Singleton Foo bindFoo(FooBarImpl impl);
@Binds Bar bindBar(Foo foo);

Assuming Foo extends Bar, you could bind Bar to Foo. Here, the singleton instance is kept in the Foo provider, injections of FooBarImpl will return new instances, and injections of Bar will consult Foo (and its caching provider) every time.

This sounds strictly worse, but there are valid use-cases: Imagine if FooBarImpl were a CacheImpl of some sort, and you want Foo and Bar to return one instance but @Named("accounts") ListeningCache and @Named("accounts") Cache to both return the same distinct instance.

Not what you want: Separate singleton bindings

For completeness:

/* not Singleton */ public class FooBarImpl implements Foo, Bar { /* ... */ }
// in your module:
@Binds @Singleton Foo bindFoo(FooBarImpl impl);
@Binds @Singleton Bar bindBar(FooBarImpl impl);

Here, Foo and Bar will each return a separate Singleton instance, and injections of FooBarImpl will return a new instance each time. This is great for use-cases where you want two or more objects, but be careful not to accidentally implement this solution when you're trying to implement the "less ideal" solution above. They look really similar, but behave very differently.

like image 82
Jeff Bowman Avatar answered Sep 18 '22 20:09

Jeff Bowman


interface FooBar extends Foo, Bar

class FooBarImpl implements FooBar {...}

@SomeScope @Binds FooBar bindsFooBar(impl: FooBarImpl)
@SomeScope @Binds Foo bindsFoo(fooBar: FooBar)
@SomeScope @Binds Bar bindsBar(fooBar: FooBar)
like image 29
Ben Lewis Avatar answered Sep 17 '22 20:09

Ben Lewis