Singleton scopes in Dagger 2

2.2k Views Asked by At

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();
  }
}
1

There are 1 best solutions below

0
On

You usually mark the top component as @Singleton and then all the subcomponents and provide*() that need it. After all, singleton can be guaranteed only on global graph level.

Starting to mark with @Singleton from 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