Can I use Dagger to inject different values into several instances of the same class that are deep inside the object graph? I want to avoid passing the values through the containing objects in the graph (so I can change the implementation of the contained objects without affecting their containers).
Here's a contrived example. The object graph is a Top, which contains a Left and Right, which each contain a Show. So there are two instances of Show.
class Top {
Left left;
Right right;
void encodeTwice(String data) {
left.encode(data);
right.encode(data.getBytes());
}
}
class Left {
Leaf leaf;
void encode(String data) {
leaf.write(URLEncoder.encode(data));
}
}
class Right {
Leaf leaf;
void encode(byte[] data) {
leaf.write(DatatypeConverter.printBase64Binary(data));
}
}
interface Leaf {
void write(String data);
}
class Show implements Leaf {
String label;
@Override public void write(String data) {
System.out.println(label + ": " + data);
}
}
// There might be other classes that implement Leaf.
Can I use Dagger to inject different values into Top.left.leaf.label and Top.right.leaf.label?
Here's one attempt. This is unsatisfactory, because Left and Right depend on the implementation of Leaf. I want to inject Show.label without involving Left or Right.
ObjectGraph.create(new TopModule()).get(Top.class).encodeTwice("Hello!");
@Module(injects = Top.class)
public class TopModule {
@Provides @Named("Left.leaf.label") String provideLeftLabel() {
return "URL encoded";
}
@Provides @Named("Right.leaf.label") String provideRightLabel() {
return "Base 64";
}
}
class Top {
@Inject Left left;
@Inject Right right;
void encodeTwice(String data) {
left.encode(data);
right.encode(data.getBytes());
}
}
class Left {
Leaf leaf;
@Inject Left(@Named("Left.leaf.label") String label) {
leaf = new Show(label);
}
void encode(String data) {
leaf.write(URLEncoder.encode(data));
}
}
class Right {
Leaf leaf;
@Inject Right(@Named("Right.leaf.label") String label) {
leaf = new Show(label);
}
void encode(byte[] data) {
leaf.write(DatatypeConverter.printBase64Binary(data));
}
}
interface Leaf {
void write(String data);
}
class Show implements Leaf {
String label;
Show(String label) {
this.label = label;
}
@Override public void write(String data) {
System.out.println(label + ": " + data);
}
}
So this is hard to reason about with such abstract types because based on what Left
, Right
, and Top
actually are I might change approaches.
There are three approaches to this problem that I can think of: a Leaf
factory, differentiation of Leaf
s by qualifier, differentiation of Leaf
s by child graphs.
Hide the Leaf
implementation behind a factory that takes its required dependencies.
interface LeafFactory {
Leaf create(String name);
}
class TopModule {
// ...
@Provide @Singleton LeafFactory provideLeafFactory() {
return new LeafFactory() {
@Override public Leaf create(String name) {
return new Show(name);
}
};
}
}
class Left {
private final Leaf leaf;
@Inject Left(@Named("..") String name, LeafFactory leafFactory) {
leaf = leafFactory.create(name);
}
// ...
}
Right now you are injecting the label via a qualifier annotation. There's no reason you couldn't do the same thing but with Leaf
instances.
class TopModule {
@Provides @Named("..") Leaf provideLeftLeaf() {
return new Show("URL encoded");
}
@Provides @Named("..") Leaf provideRightLeaf() {
return new Show("Base64 encoded");
}
}
class Left {
@Inject Left(@Named("..") Leaf leftLeaf) { .. }
}
Note: You can still inject the label names as arguments to the provideFooLeaf
methods if you need that abstraction as well.
By extending the graph with a "leaf scope" you can create separate parts of your application (e.g., "right", "left") which only have a single Leaf
instance.
@Module(addsTo = TopModule.class)
class LeafModule {
private final String label;
LeafModule(String label) {
this.label = label;
}
@Provide @Singleton Leaf provideLeaf() {
return new Show(label);
}
}
Now we can .plus()
this onto the root object graph to get a scope.
ObjectGraph ogRoot = ObjectGraph.create(new TopModule());
// ...
ObjectGraph ogLeft = ogRoot.plus(new LeafModule("URL encoded"));
Leaf left = ogLeft.get(Leaf.class);
ObjectGraph ogRight = ogRoot.plus(new LeafModule("Base64 encoded"));
Leaf right = ogRight.get(Leaf.class);
Like I said, this is hard to generalize to fit the abstract sample app you've outlined. But I hope you can see how child graphs allow you create a specialized version of the graph which is a superset of the "root" graph. By doing this multiple times you can alter the dependencies which are used to inject your types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With