Closer look at scopes in Dagger 2
Table of Contents
This is the last part of a write up about Dagger 2 on Android.
- Part1: Dependency injection on Android
- Part2: Dagger 2 usage on Android
- Part3: Closer look at scopes in Dagger 2 (this)
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
everytime 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 everytime someone calls logout()/login()
.