26

I probably missed something, but I thought Scopes like @Singleton are used to define "scoped lifecycles".

I use Dagger 2 in an Android app (but I don't think the problem is android related at all).

I have 1 Module:

@Module public class MailModule {

  @Singleton @Provides public AccountManager providesAccountManager() {
    return new AccountManager();
  }

  @Singleton @Provides public MailProvider providesMailProvider(AccountManager accountManager) {
    return new MailProvider(accountManager);
  }
}

I have two different components with @Singleton scope:

@Singleton
@Component(modules = MailModule.class)
public interface LoginComponent {

  public LoginPresenter presenter();
}


@Singleton
@Component(
    modules = MailModule.class
)
public interface MenuComponent {

  MenuPresenter presenter();

}

Both, MenuPresenter and LoginPresenter, have an @Inject constructor. While MenuPresenter expects MailProvider as parameter, LoginPresenter takes an AccountManager:

  @Inject public MenuPresenter(MailProvider mailProvider) { ... }

  @Inject public LoginPresenter(AccountManager accountManager) { ... }

But every time I use the components to create a MenuPresenter or LoginPresenter I get a fresh new instance of MailProvider and AccountManager. I thought they were in the same scope and should therefore be kind of singleton (in the same scope).

Did I understand something completely wrong. How do I define a real singleton for multiple components in dagger 2?

sockeqwe
  • 15,574
  • 24
  • 88
  • 144

2 Answers2

53

I assume that LoginComponent and MenuComponent are used separately, e.g. in LoginActivity and MenuActivity. Each component is built in Activity.onCreate. If so, components are recreated every time new activity created, modules and dependencies too, independent of what scope they bond to. Therefore, you get new instances of MainProvider and AccountManager every time.

MenuActivity and LoginActivity have separate livecycles, so dependencies from MailModule cannot be singleton in both of them. What you need is to declare root component with @Singleton scope (e.g. in Application subclass), make MenuComponent and LoginComponent depend on it. Activity level component cannot be @Singleton scoped, better to create your own scopes using @Scope annotation, e.g.:

@Retention(RetentionPolicy.RUNTIME)
@Scope
public @interface MenuScope {
}

Or you can leave them unscoped.

Regarding scopes at all here's brief from initial Dagger 2 proposal:

@Singleton
@Component(modules = {…})
public interface ApplicationComponent {}

That declaration enables dagger to enforce the following constraints:

  • A given component may only have bindings (including scope annotations on classes) that are unscoped or of the declared scope. I.e. a component cannot represent two scopes. When no scope is listed, bindings may only be unscoped.
  • A scoped component may only have one scoped dependency. This is the mechanism that enforces that two components don’t each declare their own scoped binding. E.g. Two Singleton components that each have their own @Singleton Cache would be broken.
  • The scope for a component must not appear in any of its transitive dependencies. E.g.: SessionScoped -> RequestScoped -> SessionScoped doesn’t make any sense and is a bug.
  • @Singleton is treated specially in that it cannot have any scoped dependencies. Everyone expects Singleton to be the “root”.

The goal of this combination of rules is to enforce that when scope is applied, components are composed with the same structure that we used to have with Dagger 1.0 plus()’d ObjectGraphs, but with the ability to have static knowledge of all of the bindings and their scopes. To put it another way, when scopes are applied, this limits the graphs than can be built to only those that can be correctly constructed.

From my own practice, it's clearer not to use @Singleton at all. Instead of that, I use @ApplicationScope. It serves to define singletons on whole application and does not have additional restrictions as @Singleton has.

Hope that helps you :). It's quite tricky to be understood quickly, takes time, for me at least it was.

Kirill Boyarshinov
  • 6,143
  • 4
  • 33
  • 29
  • But a component can only have one Scope annotation, right? How would I do that if I have an application component with `@Application` and LoginComponent with `@Activity` scope? – sockeqwe Apr 14 '15 at 08:45
  • 2
    Right. Component cannot be annotated with two scopes. Activity scope component would have all dependencies from application scope component if it is defined in annotation `@Component(dependencies = ApplicationComponent.class)`. Think of components with scopes as graphs and subgraphs. Application component and its scope - root graph, activity component and its scope - root's subgraph. – Kirill Boyarshinov Apr 14 '15 at 08:56
  • Does avoiding usage of ```@Singleton``` scope allow to inject ```@Application``` dependencies into ```@Activity``` scope? Example would be, if MyPresenter is of ```@Application``` scope and I want to inject it into MyActivity which is of ```@Activity``` scope. – AAverin Jun 11 '15 at 09:37
  • @AAverin it is. But I think it can be achieved by using `@Singleton` too. – Kirill Boyarshinov Jun 11 '15 at 11:41
  • This is by far the best explanation I have found !! +1 – Lisa Anne Nov 02 '16 at 20:53
  • "@Singleton is treated specially in that it cannot have any scoped dependencies. Everyone expects Singleton to be the “root" Does Dagger 2 actually enforce this? – Florian Walther Apr 06 '19 at 21:32
7

You can do the following to define a real singleton for multiple components. I am assuming @ApplicationScoped and @ActivityScoped to be the different scopes.

@Module public class MailModule {
  @Provides @ApplicationScoped 
  public AccountManager providesAccountManager() {
    return new AccountManager();
  }

  @Provides @ApplicationScoped
  public MailProvider providesMailProvider(AccountManager accountManager) {
        return new MailProvider(accountManager);
  }
}

Then a MailComponent can be defined for the MailModule. The LoginComponent and MenuComponent can depend on the MailComponent.

@ApplicationScoped
@Component(modules = MailModule.class)
public interface MailComponent {
  MailProvider mailProvider();
  AccountManager accountManager();
}

@ActivityScoped
@Component(dependencies = MailComponent.class)
public interface LoginComponent {
  LoginPresenter presenter();
}

@ActivityScoped
@Component(dependencies = MailComponent.class)
public interface MenuComponent {
  MenuPresenter presenter();
}

The MailComponent can be initialized as shown below and can be used in MenuComponent and LoginComponent again shown below.

MailComponent mailComponent = DaggerMailComponent.builder().build();

DaggerMenuComponent.builder().mailComponent(mailComponent).build();

DaggerLoginComponent.builder().mailComponent(mailComponent).build()            
Praveer Gupta
  • 3,940
  • 2
  • 19
  • 21
  • 1
    Is there any difference between writing `@ApplicationScoped ` on top each method of Module separetly and writing it on top of @Module once? – Jemshit Dec 21 '15 at 13:00
  • 2
    @JemshitIskenderov - Putting `@ApplicationScoped` annotation top of `@Module` will have no effect. The purpose of a module is to provide dependencies through provider methods (methods annotated with `@Provides` annotation). These providers methods may or may not have scopes. Thus it becomes important to define scopes at provider method level. – Praveer Gupta Dec 22 '15 at 08:02