Skip to content

Scope Annotations

Stéphane Nicolas edited this page Jul 16, 2019 · 18 revisions

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.

Scopes & scope annotations

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.

Custom scope annotations

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 scope s that supports this scope annotation will be used in the following bullet points.
  • create an instance of Bar inside the scope s.
  • recycle this instance for every @Inject Bar or scope.getInstance(Bar.class) statements if and only if Bar is also annotated with @Singleton.
  • make sure that Bar only has dependencies in the scope s or its parents.

Scope annotations and scoped bindings

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.

Scope annotations and scope names

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.

Scope annotations and providers

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.

A note on the @Singleton annotation

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.

Links