-
Notifications
You must be signed in to change notification settings - Fork 115
Scope Annotations
Scope annotations are an alternative to defining bindings, more static but powerful and convenient. Note that in TP3, both bindings and annotations offer the same expressivity and granularity.
Scope annotations apply to classes in Toothpick.
A scope annotation can be 'supported' by a scope.
For instance, to express that an activity scope supports the @ActivityScope
annotation, we can write:
Toothpick.openScope(activity)
.supportScopeAnnotation(ActivityScope.class);
Then, if a class is annotated by a scope annotation such as
@ActivityScope
class Foo {}
Toothpick will enforce that Foo
can only be injected or created inside a scope that supports the @ActivityScope
annotation. This allows to enforce design constraint and fine tune memory usage.
The JSR 330 defines a mechanism to create custom scope annotations. Note that TP doesn't define any scope annotation itself. The JSR offers one, but this will be discussed further down.
//a custom scope annotation
@javax.inject.Scope
@Documented
@Retention(RUNTIME)
interface @ActivityScope {}
//a class using the custom scope annotation @ActivityScope
@ActivityScope
class Bar {...}
A scope annotation is an annotation class that is qualified by the javax.inject.Scope
annotation, such as the @ActivityScope
class above. Scope annotations must have RUNTIME retention. This is needed to perform runtime checks in Toothpick.
Annotating Bar
with @ActivityScope
means "All instances of Bar
should belong to a scope that supports the scope annotation @ActivityScope
". All the dependencies of Bar
will also be found in such a scope or its parents.
If we want to make Bar
a singleton of such a scope, we can use:
@ActivityScope @Singleton
class Bar {...}
Note that this is not the same behavior as Dagger, or Guice. TP 3 diverges on this point and interprets the JSR 330 differently: scopes and singletons are different things in TP3, whereas these notions are interleaved in most other DI engines.
When the statement scope.getInstance(Bar.class)
is executed, TP will :
- make sure that scope or one of its parents supports the annotation
@ActivityScope
. The first scopes
that supports this scope annotation will be used in the following bullet points. - create an instance of
Bar
inside the scopes
. - recycle this instance for every
@Inject Bar
orscope.getInstance(Bar.class)
statements if and only ifBar
is also annotated with@Singleton
. - make sure that
Bar
only has dependencies in the scopes
or its parents.
Scope annotations allow to :
- create a binding
- AND scope the binding in a given scope
- AND recycle the instance between injections (if combined with
@Singleton
).
Using scope annotations is exactly the same as defining a binding in a scope that supports this scope annotation.
The annotation way is more expressive, it is generally useful for "static scopes", those with a constant name.
From now on, we will use the following convention in diagrams to say that a scope annotation is bound to a given scope :
Scope S0 = @ApplicationScope //bound to the scope annotation ApplicationScope
\
\
Scope S1 // not bound to any scope annotation
\
\
@S2Singleton //a scope bound to the scope annotation @S2Singleton
Note that it is possible to support multiple annotations for a scope and to support the same scope annotation in multiple scopes. We do not recommend these practices though.
A scope has a name in Toothpick. It is required to open/close/release it :
Scope s = Toothpick.open(<name>);
If the name of a scope is a scope annotation class, then this scope automatically supports this scope annotation.
//custom scope annotation
@Scope
interface @ViewModelScope {}
//bind a scope to a scope annotation, by name
Scope viewModelScope = Toothpick.open(ViewModelScope.class);
The scope viewModelScope
will automatically support the scope annotation @ViewModelScope
. It is strictly equivalent to
//bind a scope to a scope annotation, by name
Scope viewModelScope = Toothpick.open(<any object>);
viewModelScope.supportAnnotationScope(ViewModelScope.class);
Both mean that any class annotated with this scope annotation will be created in viewModelScope
.
Example :
Scope s0 = @ActivityScope
\
\
Scope s1
@ActivityScope class Foo {...}
class Bar {...}
class Qurtz {
@Inject Foo foo;
@Inject Bar bar;
}
Toothpick.inject(new Qurtz(), s1);
In the example above, the Qurtz
instance will receive a new Bar
instance created in the scope s1
, and the Foo
instance will be created in the scope s0
as it supports @ActivityScope
.
Provider classes can be annotated:
@ActivityScope @Singleton
class FooProvider implements Provider<Foo> {...}
//it is equivalent to defining this binding in a scope that supports the @ActivityScope annotation
bind(Foo.class).toProvider(FooProvider.class).singleton();
scope.getInstance(Foo.class);
In this case, the scope verification mechanism is enforced for creating a singleton instance of the FooProvider
class. The same provider instance will be reused to create all injected instances of Foo
. When a provider belongs to a scope, all the instances it provides will also belong to this scope.
Note that, in the example above, the instances of Foo
will not be singletons themselves. If we want to express that not only the provider will be a singleton but also the produced Foo
instance itself, we can use the extra annotation:
@ActivityScope @Singleton @ProvidesSingleton
class FooProvider implements Provider<Foo> {...}
It is equivalent to using the following binding:
bind(Foo.class).toProvider(FooProvider.class).providesSingleton().singleton();
Using the binding above, FooProvider
doesn't need to be annotated with any of the annotation. We recommend to avoid mixing annotations and bindings declarations, though TP supports it and will take both into account.
The most famous scope annotation is probably the @javax.inject.Singleton
annotation.
//a class using the scope annotation @Singleton
@Singleton
class Foo {...}
This annotation is simply understood as "create a single instance of the Foo
class", and this remains true in Toothpick. Foo
will have a single instance and will be available in the root scope and all its children.
In the JSR 330, @Singleton
is considered as a scope annotation. In TP 3, we consider it as different: it simply means that a single class instance should be recycled accross many injection requests. In TP 3, the @Singleton
annotation can be combined with other another scope annotation to express that a class is to be instanciated once and only once and that it belongs to a specific scope.
Note : In the JSR 330 standard classes, @Singleton
is defined as
@javax.inject.Scope
@Documented
@Retention(RUNTIME)
interface @Singleton {}
We have kept it in TP 3, and TP 3 honors the meaning of @Singleton
when used by itself, with no other scope annotations. The scope annotation is then implicit and denotes the root scope. This ensures both backward and conceptual compatibility with the JSR and other DI engines.