I am trying to set up the following in Dagger 2:
An EventFinder has two parts: a TriggerFinder and an ArgFinder each of which have many different implementations which are themselves complex objects with many dependencies. The code sample below successfully builds an EventFinder with sample TriggerFinder and ArgFinder which both depend on another object, WordNet. However, WordNet is a very big, expensive, constant object I would like to share between models as a singleton. The current implementation of WithWordNetEventFinderComponent loads up WordNet twice. If I try marking the @Provides method in WordNetModule as @Singleton, I get the error:
Error: ArgFinderUsingWordNetComponent (unscoped) may not reference scoped bindings:
@Singleton @Provides WordNet WordNetModule.provideWordNet()
But propagating @Singleton annotations up the tree of components only results in other errors. What if the correct way to do this?
Full code:
import org.junit.Test;
import javax.inject.Inject;
import javax.inject.Named;
import dagger.Component;
import dagger.Module;
import dagger.Provides;
// WordNet - this is the expensive shared resource we would
// like to make a singleton
class WordNet {
final String path;
public WordNet(@Named("path") String path) {
System.out.println("Loading Fake wordnet from " + path);
this.path = path;
}
}
@Module
class WordNetModule {
final String path;
public WordNetModule(String path) {
this.path = path;
}
// uncommenting the line below causes errors
//@Singleton
@Provides
public WordNet provideWordNet() {
return new WordNet(path);
}
}
interface TriggerFinder {
}
class TriggerFinderUsingWordnet implements TriggerFinder {
WordNet wordNet;
@Inject
public TriggerFinderUsingWordnet(WordNet wordNet) {
this.wordNet = wordNet;
}
}
@Module(includes = WordNetModule.class)
class TriggerFinderWithWordnetModule {
@Provides
public TriggerFinder provideTriggerFinder(TriggerFinderUsingWordnet triggerFinder) {
return triggerFinder;
}
}
interface ArgFinder {
}
class ArgFinderUsingWordnet implements ArgFinder {
WordNet wordNet;
@Inject
public ArgFinderUsingWordnet(WordNet fakeWordNet) {
this.wordNet = fakeWordNet;
}
}
@Module(includes = WordNetModule.class)
class ArgFinderWithWordNetModule {
@Provides
public ArgFinder provideArgFinder(ArgFinderUsingWordnet argFinder) {
return argFinder;
}
}
// the composite object we wish to create
class EventFinder {
private final TriggerFinder triggerFinder;
private final ArgFinder argFinder;
@Inject
public EventFinder(TriggerFinder triggerFinder, ArgFinder argFinder) {
this.triggerFinder = triggerFinder;
this.argFinder = argFinder;
}
}
// components to wire everything together
interface ArgFinderComponent {
ArgFinder argFinder();
}
interface TriggerFinderComponent {
TriggerFinder triggerFinder();
}
@Component(modules = ArgFinderWithWordNetModule.class)
interface ArgFinderUsingWordNetComponent extends ArgFinderComponent {
}
@Component(modules = TriggerFinderWithWordnetModule.class)
interface TriggerFinderUsingWordNetComponent extends TriggerFinderComponent {
}
interface EventFinderComponent {
EventFinder eventFinder();
}
@Component(dependencies = {ArgFinderUsingWordNetComponent.class,
TriggerFinderUsingWordNetComponent.class})
interface WithWordNetEventFinderComponent extends EventFinderComponent {
}
public class DaggerComponentTest {
@Test
public void withWordNetTest() {
final WordNetModule wordNetModule = new WordNetModule("myPath");
DaggerWithWordNetEventFinderComponent.builder()
.argFinderUsingWordNetComponent(
DaggerArgFinderUsingWordNetComponent.builder().wordNetModule(wordNetModule).build())
.triggerFinderUsingWordNetComponent(
DaggerTriggerFinderUsingWordNetComponent.builder().wordNetModule(wordNetModule).build())
.build().eventFinder();
}
}
You usually mark the top component as
@Singletonand then all the subcomponents andprovide*()that need it. After all, singleton can be guaranteed only on global graph level.Starting to mark with
@Singletonfrom bottom will lead to multiple errors (as you experienced) until you get to the very top. So probably it will be easier to rollback and start from the top@Component