I'm trying to wrap my head around scopes in Dagger 2, specifically the lifecycle of scoped graphs. How do you create a component that will be cleaned up when you leave the scope.
In the case of an Android application, using Dagger 1.x you generally have a root scope at the application level which you'd extend to create a child scope at the activity level.
public class MyActivity {
private ObjectGraph mGraph;
public void onCreate() {
mGraph = ((MyApp) getApplicationContext())
.getObjectGraph()
.plus(new ActivityModule())
.inject(this);
}
public void onDestroy() {
mGraph = null;
}
}
The child scope existed as long as you kept a reference to it, which in this case was the lifecycle of your Activity. Dropping the reference in onDestroy ensured the scoped graph was free to be garbage collected.
EDIT
Jesse Wilson recently posted a mea culpa
Dagger 1.0 badly screwed up its scope names ... The @Singleton annotation is used for both root graphs and custom graphs, so it's tricky to figure out what the actual scope of a thing is.
and everything else I've read/heard points towards Dagger 2 improving the way scopes work, but I'm struggling to understand the difference. According to @Kirill Boyarshinov's comment below, the lifecycle of a component or dependency is still determined, as usual, by concrete references. So is the difference between Dagger 1.x and 2.0 scopes purely a matter of semantic clarity?
Dependencies were either @Singleton
or not. This was equally true of dependencies in the root graph and subgraphs, leading to ambiguity as to which graph the dependency was bound to (see In Dagger are Singletons within the sub-graph cached or will they always be recreated when a new activity sub-graph is constructed?)
Custom scopes allow you to create semantically clear scopes, but are functionally equivalent to applying @Singleton
in Dagger 1.x.
// Application level
@Singleton
@Component( modules = MyAppModule.class )
public interface MyAppComponent {
void inject(Application app);
}
@Module
public class MyAppModule {
@Singleton @Named("SingletonScope") @Provides
StringBuilder provideStringBuilderSingletonScope() {
return new StringBuilder("App");
}
}
// Our custom scope
@Scope public @interface PerActivity {}
// Activity level
@PerActivty
@Component(
dependencies = MyAppComponent.class,
modules = MyActivityModule.class
)
public interface MyActivityComponent {
void inject(Activity activity);
}
@Module
public class MyActivityModule {
@PerActivity @Named("ActivityScope") @Provides
StringBuilder provideStringBuilderActivityScope() {
return new StringBuilder("Activity");
}
@Name("Unscoped") @Provides
StringBuilder provideStringBuilderUnscoped() {
return new StringBuilder("Unscoped");
}
}
// Finally, a sample Activity which gets injected
public class MyActivity {
private MyActivityComponent component;
@Inject @Named("AppScope")
StringBuilder appScope
@Inject @Named("ActivityScope")
StringBuilder activityScope1
@Inject @Named("ActivityScope")
StringBuilder activityScope2
@Inject @Named("Unscoped")
StringBuilder unscoped1
@Inject @Named("Unscoped")
StringBuilder unscoped2
public void onCreate() {
component = Dagger_MyActivityComponent.builder()
.myApplicationComponent(App.getComponent())
.build()
.inject(this);
appScope.append(" > Activity")
appScope.build() // output matches "App (> Activity)+"
activityScope1.append("123")
activityScope1.build() // output: "Activity123"
activityScope2.append("456")
activityScope1.build() // output: "Activity123456"
unscoped1.append("123")
unscoped1.build() // output: "Unscoped123"
unscoped2.append("456")
unscoped2.build() // output: "Unscoped456"
}
public void onDestroy() {
component = null;
}
}
The takeaway being that using @PerActivity
communicates your intention regarding the lifecycle of this component, but ultimately you can use the component anywhere/anytime. Dagger's only promise is that, for a given component, scope annotated methods will return a single instance. I also assume Dagger 2 uses the scope annotation on the component to verify that modules only provide dependencies that are either in the same scope or non-scoped.
Dependencies are still either singleton or non-singleton, but @Singleton
is now intended for application-level singleton instances and custom scopes are the preferred method for annotating singleton dependencies with a shorter lifecycle.
The developer is responsible for managing the lifecycle of components/dependencies by dropping references that are no longer needed and responsible for ensuring that components are only created once in the scope for which they are intended, but custom scope annotations make it easier to identify that scope.
Is my understanding of Dagger 2 scopes and lifecycles correct?
* Not actually a $64'000 question.
For each of the @Provides annotated function, dagger will generate a factory class, whose job is to create and return a provider to the component. Since Factory is a subtype of Provider, each Factory class can returns an instance of itself as Provider via the create function.
Dagger is a fully static, compile-time dependency injection framework for Java, Kotlin, and Android. It is an adaptation of an earlier version created by Square and now maintained by Google.
Dagger is arguably the most used Dependency Injection, or DI, framework for Android. Many Android projects use Dagger to simplify building and providing dependencies across the app. It gives you the ability to create specific scopes, modules, and components, where each forms a piece of a puzzle: The dependency graph.
As for your question
What determines the lifecycle of a component (object graph) in Dagger 2?
The short answer is you determine it. Your components can be given a scope, such as
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
These are useful for you for two things:
.
@Component(modules={ApplicationModule.class})
@ApplicationScope
public interface ApplicationComponent {
Something something();
AnotherThing anotherThing();
void inject(Whatever whatever);
}
@Module
public class ApplicationModule {
@ApplicationScope //application-scoped provider, only one can exist per component
@Provides
public Something something() {
return new Something();
}
@Provides //unscoped, each INJECT call creates a new instance
public AnotherThing anotherThing() {
return new AnotherThing();
}
}
This can be done with @Subcomponent
annotation, or component dependencies. I personally prefer dependencies.
@Component(modules={ApplicationModule.class})
@ApplicationScope
public interface ApplicationComponent {
Something something();
AnotherThing anotherThing();
void inject(Whatever whatever);
ActivityComponent newActivityComponent(ActivityModule activityModule); //subcomponent factory method
}
@Subcomponent(modules={ActivityModule.class})
@ActivityScope
public interface ActivityComponent {
ThirdThingy thirdThingy();
void inject(SomeActivity someActivity);
}
@Module
public class ActivityModule {
private Activity activity;
public ActivityModule(Activity activity) {
this.activity = activity;
}
//...
}
ApplicationComponent applicationComponent = DaggerApplicationComponent.create();
ActivityComponent activityComponent = applicationComponent.newActivityComponent(new ActivityModule(SomeActivity.this));
Or you can use component dependencies like so
@Component(modules={ApplicationModule.class})
@ApplicationScope
public class ApplicationComponent {
Something something();
AnotherThing anotherThing();
void inject(Whatever whatever);
}
@Component(dependencies={ApplicationComponent.class}, modules={ActivityModule.class})
@ActivityScope
public interface ActivityComponent extends ApplicationComponent {
ThirdThingy thirdThingy();
void inject(SomeActivity someActivity);
}
@Module
public class ActivityModule {
private Activity activity;
public ActivityModule(Activity activity) {
this.activity = activity;
}
//...
}
ApplicationComponent applicationComponent = DaggerApplicationComponent.create();
ActivityComponent activityComponent = DaggerActivityComponent.builder().activityModule(new ActivityModule(SomeActivity.this)).build();
Important things to know:
A scoped provider creates one instance for that given scope for each component. Meaning a component keeps track of its own instances, but other components don't have a shared scope pool or some magic. To have one instance in a given scope, you need one instance of the component. This is why you must provide the ApplicationComponent
to access its own scoped dependencies.
A component can subscope only one scoped component. Multiple scoped component dependencies are not allowed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With