Reputation: 13
I read a lot of material on the @Scope annotation but still not fully understanding if what I know is correct.
Can someone explain what exactly is @Scope for? Where is it used? How is it used?
Is it only used when defining the component?
Also is a scope defined like this?:
@Scope
public @interface SomeScope {
}
And used like this?:
@SomeScope
@Component(modules = {
HeaterModule.class,
PumpModule.class
})
public interface MainActivityComponent {
void inject(MainActivity mainActivity);
CoffeeMaker maker();
}
Upvotes: 1
Views: 1543
Reputation: 2452
The definition of scopes for Java EE applications states the following:
For a web application to use a bean that injects another bean class, the bean needs to be able to hold state over the duration of the user’s interaction with the application. The way to define this state is to give the bean a scope.
Let's associate it with Android. Scopes impose certain limitations in terms of dependencies lifecycle provided by a scoped provider.
For example, let's say we want to be provided with @Singleton
dependency obtainable from @ActivityScope
d component. The component lives as long as the Activity. Once activity is destroyed and created again, our component is instantiated accordingly and our '@Singleton' dependency is also created once again.
Summing up, our @Singleton
dependencies live as long as the @Components
they are associated with - this is I think most practical explanation.
Internally, once Dagger notifies within the @Component
the provision method with the specified scope, it creates a ScopedProvider for dependency provision. Otherwise, a Factory is created.
The ScopedProvider
's get method is a double-check singleton method:
public T get() {
// double-check idiom from EJ2: Item 71
Object result = instance;
if (result == UNINITIALIZED) {
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
instance = result = factory.get();
}
}
}
return (T) result;
}
To see a practical @Scope
usage you can take a look at one of the examples of mine:
https://github.com/dawidgdanski/AccountAuthenticatorExample
Hope, that was helpful somehow.
EDIT 1: The article provided by @Fshamri explains it even better.
EDIT 2:
Let's consider the following structure :
The Component:
@ActivityScope //Dagger ignores the annotation put atop the @Component. I put it there just for readability
@Component(dependencies = DependencyGraph.class,
modules = {ActivityModule.class})
public interface ActivityComponent {
void inject(MainActivity mainActivity);
void inject(SignUpActivity signUpActivity);
void inject(SignInActivity signInActivity);
And the module supplying the dependencies:
@Module
public class ActivityModule {
private final Activity activity;
public ActivityModule(Activity activity) {
this.activity = activity;
}
@Provides
@ActivityScope
MainView provideMainView() {
return (MainView) activity;
}
@Provides
SignInView provideSignInView() {
return (SignInView) activity;
}
}
The @ActivityScope extends the @Scope annotation - it is interpreted by Dagger the same way as the @Singleton is. The ActivityModule
provides 2 views: the @ActivityScope
d MainView
and the SignInView
without any scope. During the compilation time Dagger's annotation processor creates Providers for both views. The difference between the MainView's and the SignInView's generated Provider is that for the MainView Dagger generates the ScopedProvider
(because we explicitly request this kind of provider with @ActivityScope
) whereas for the SignInView
Dagger generates regular Factory
provider.
The contract of the ScopedProvider looks like the one above. The contract for the Factory
provider, however, looks the following way:
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class ActivityModule_ProvideSignInViewFactory implements Factory<SignInView> {
private final ActivityModule module;
public ActivityModule_ProvideSignInViewFactory(ActivityModule module) {
assert module != null;
this.module = module;
}
@Override
public SignInView get() {
SignInView provided = module.provideSignInView();
if (provided == null) {
throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
}
return provided;
}
public static Factory<SignInView> create(ActivityModule module) {
return new ActivityModule_ProvideSignInViewFactory(module);
}
}
Conclusions:
1. The ScopedProvider
is a classical double-check singleton pattern. It guarantees to create only one instance of the dependency.
2. The Factory
's get()
method just distributes the dependency from the module which means it can distribute new instance of the dependency each time it is requested (in fact, in the example the Activity is only cast to the SignInView which still gives us a single instance but this is copy-pasted logic from my example).
3. Dagger cares about @Scopes provided along with provision methods in the @Modules, not the @Components.
I encourage you to download the sample and build the project, then to see the contract of the generated Dagger
components and modules.
Hope this is more understandable now.
Upvotes: 2
Reputation: 1386
have you looked at this article?
In Dagger 2 scopes mechanism cares about keeping single instance of class as long as its scope exists. In practice it means that instances scoped in @ApplicationScope lives as long as Application object. @ActivityScope keeps references as long as Activity exists (for example we can share single instance of any class between all fragments hosted in this Activity). In short - scopes give us “local singletons” which live as long as scope itself.
Upvotes: 2