Closer look at scopes in Dagger 2

This is the last part of a write up about Dagger 2 on Android.

All usages of Dagger below refer to Dagger 2

Scopes

In Dagger 2 we can use the @Singleton scope from JSR 330 or we can define our own scopes. Custom scopes are defined as annotations like this.

@Scope
@Documented
@Retention(value=RetentionPolicy.RUNTIME)
public @interface CustomScope {
}

We use custom scope when we want to reuse dependencies for a custom amount of time. For example, we could create a @ActivityScope for dependencies that only should be available for the lifecycle of an Activity. Or @UserScope for all dependencies tied to a user session.

It's however important to understand that custom scopes work exactly like @Singleton. Any dependency annotated with any scope will be reused as long as we use the corresponding component. There is no magic involved, we must manually create and throw away component in accordance with our wanted lifecycle.

We could even use @Singleton on all dependencies and still have custom lifecycle. Dagger will however throw helpful build exceptions if we mix different scopes. So, custom scopes will be valuable both as documentation and to find mistakes early.

We can use components directly or use the concept of subcomponents to deal with our wanted lifecycle.

Custom scope through components

Custom scoped components work exactly like the components we looked at in the earlier parts. It's an interface that we annotate with @Component and our custom scope.

@CustomScope
@Component(modules = { MyModule.class })
public interface CustomComponent {
    Dependency getDependency();
    void inject(MyActivity act);
}

Many times, when we work with custom scopes we use more than one component. One for singleton dependencies and one for our custom scoped dependencies for example. In those cases, it might be useful to use the dependency attribute on @Component to inherit between components.

@Singelton
@Component(modules = { MyModule.class })
public interface MyComponent {
    Dependency getDependency();
}

@CustomScope
@Component(dependencies = { MyComponent.class }, modules = { MyOtherModule.class })
public interface CustomComponent {
    void inject(MyActivity act);
}

In the example above our CustomComponent will be able to inject Dependency from MyComponent.getDependency() and everything exposed in MyOtherModule into MyActivity.

Subcomponent

Subcomponents is defined with @Subcomponent. They require a normal component to act as parent when we want to create an instance.

@Singelton
@Component(modules = { MyModule.class })
public interface MyComponent {
    CustomSubcomponent plus(MyOtherModule module);
}

@CustomScope
@Subcomponent(modules = { MyOtherModule.class })
public interface CustomSubcomponent {
    void inject(MyActivity act);
}

The difference between subcomponent and using dependencies attribute on component is that a subcomponent will inherit everything from the modules in the parent component, not just what the parent component exposes.

Custom components examples

Component in Activity lifecycle

In this example, we create a component that lives as long as the Activity does.

@Scope
@Documented
@Retention(value=RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

@Module
public class AppModule {

    @Provides
    @Singleton
    Dependency1 provideDependency1() {
        return new Dependency1();
    }
}

@Singleton
@Component(modules = { AppModule.class })
public interface AppComponent {
    Dependency1 getDependency1();
}

@Module
public class ActivityModule {

    @Provides
    @ActivityScope
    Dependency2 provideDependency2() {
        return new Dependency2();
    }
}

@ActivityScope
@Component(dependencies = { AppComponent.class }, modules = {ActivityModule.class })
public interface ActivityComponent {
    void inject(MyActivity act);
}

public class MyApp extends Application {

    private AppComponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        appComponent = DaggerAppComponent.create();
    }

    public AppComponent getComponent() {
        return appComponent;
    }
}

public class MyActivity extends Activity {

    @Inject
    Dependency1 dependency1;

    @Inject
    Dependency2 dependency2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityComponent component = DaggerActivityComponent.builder()
                        .appComponent(((MyApp) getApplication()).getComponent())
                        .build();

        component.inject(this);
    }
}

In the example above MyActivity will have a new instance of Dependency2 every time the activity is restarted while Dependency1, which is provided by AppComponent, will be the same instance.

Sub-component for user session

In this example, we'll use a sub-component instead to create a component that lives for as long as the user is logged in to a service.

@Scope
@Documented
@Retention(value=RetentionPolicy.RUNTIME)
public @interface UserScope {
}

@Module
public class AppModule {

    @Provides
    @Singleton
    Dependency1 provideDependency1() {
        return new Dependency1();
    }
}

@Singleton
@Component(modules = { AppModule.class })
public interface AppComponent {
    UserComponent plus(UserModule userModule);
}

@Module
public class UserModule {

    ...

    @Provides
    @UserScope
    UserObject provideUserObject() {
        return new UserObject(username);
    }
}

@UserScope
@Subcomponent(modules = { UserModule.class })
public interface UserComponent {
    void inject(MyActivity act);
}

class MyApp extends Application {

    private AppComponent appComponent;
    private UserComponent userComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        appComponent = DaggerAppComponent.create();
    }

    public AppComponent getComponent() {
        return appComponent;
    }

    public UserComponent getUserComponent() {
        return userComponent;
    }

    public void login(String username) {
        userComponent = appComponent.plus(new UserModule(username));
    }

    public void logout() {
        userComponent = null;
    }
}

public class MyActivity extends Activity {

    @Inject
    Dependency1 dependency1;

    @Inject
    UserObject userObject;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ((MyApp) getApplication()).getUserComponent().inject(this);
    }
}

In the example above MyActivity will get Dependency1 from AppModule through AppComponent even though AppComponent doesn't expose it directly.

It will also get UserObject from UserModule which will be a new instance every time someone calls logout()/login().