-
Notifications
You must be signed in to change notification settings - Fork 115
Factories and Member Injectors
In Toothpick, a Factory<Foo>
is a class that produces instances of Foo
.
As Toothpick does not rely on reflection at all, with the idea in mind that reflection performance is awfully slow on Android, it uses Factory
classes that are generated by an annotation processor.
The factories enable to bypass reflection to access a given constructor.
Toothpick annotation processor will create a factory for all classes that have an injected constructor :
public class Foo {
@Inject Foo() {...}
}
Toothpick will then generate a factory :
//a simplified Factory
public final class Foo$$Factory implements Factory<Foo> {
@Override
public Foo createInstance() {
return new Foo();
}
}
If a class has a @Inject
annotated members such as
class Foo {
@Inject Bar bar;
}
Then Toothpick will create a factory:
- for the class
Foo
If a class has a @Inject
annotated constructor with parameters such as
class Foo {
@Inject Foo(Bar bar);
}
Then Toothpick will create a factory:
- for the class
Foo
If a class is annotated with a scope annotation such as
@Singleton
class Foo {
}
Then Toothpick will create a factory:
- for the class
Foo
When a class is annotated with a custom scope annotation, the generated factory will be scoped. You will need to pass extra annotation processor parameters to TP for this to work, see section Annotation processor options.
Example:
@ActivityScope
class Foo {
@Inject Bar bar;
}
Then Toothpick will create a scoped factory:
//a scoped Factory
public final class Foo$$Factory implements Factory<Foo> {
@Override
public Foo createInstance(Scope scope) {
scope = scope.getParentScope(ActivityScope.class);
Bar bar = scope.getInstance(Bar.class);
return new Foo(bar);
}
}
The factory will create all the dependencies of the instance of Foo
inside the scope that is bound to the annotation scope. This ensures that such classes can only be produced within the adequate scope.
As a consequence, it is impossible to use a scope annotated class outside of its scope via Toothpick, no binding can override this scope declaration. The only workaround, which is not a good practice, is to create an instance of this class manually, when possible. But such code would be outside of the scope of TP.
Also, if a class is annotated with @Singleton
, its instance created by a scoped factory will not only be created inside the scope, it will also be reused inside the scope and its children scopes. Singletons can also be releasable (see section Releasable Singletons).
Scoped factories are a key component of Scope Resolution. Please refer to this page to understand how scoped factories and scope resolution indeed create a scope verification system that contributes to decrease memory leaks in applications.
In Toothpick, a MemberInjector<Foo>
is a class that injects the fields of the instances of Foo
. It can either assign injected fields or invoke injected methods.
Again, as Toothpick does not rely on reflection at all, with the idea in mind that performance is awfully slow on Android, it uses MemberInjector
classes that are generated by an annotation processor. The member injectors enable to bypass reflection to access a given field or method.
Toothpick annotation processor will create a Member Injector for all classes that have an injected member (field or method):
public class Foo {
@Inject void m(Bar bar) {...}
}
Toothpick will then generate a member injector :
//a Member Injector
public final class Foo$$MemberInjector implements MemberInjector<Foo> {
@Override
public void inject(Foo foo, Scope s) {
Bar bar = Toothpick.getInstance(Bar.class);
return foo.m(bar);
}
}
Toothpick will always inject all the dependencies (expressed by injected constructors, injected fields or injected methods) of all instances it creates.
To ensure that this rule always applies, factories use member injectors.
Example:
public class Foo {
@Inject Bar bar; //an injected field
@Inject Foo() {...} //an injected constructor
}
Toothpick will then generate a factory :
//a simplified Factory
public final class Foo$$Factory implements Factory<Foo> {
@Override
public Foo createInstance(Scope scope) {
Foo foo = new Foo();
new Foo$$MemberInjector().inject(foo, scope);
return foo;
}
}
This mechanism also enforces that all dependencies of the scope annotated classes are created in the right scope, when we combine this feature and scoped factories.
Inheritance is taken into account by member injectors. A member injector of a class Foo
will use member injectors of the super classes of Foo
if needed.
Example:
class Foo {
@Inject Bar bar;
}
class FooChild extends Foo {
@Inject Qurtz qurtz;
}
In this case
- the member injector of
FooChild
will use the member injector ofFoo
. Any instance ofFooChild
will be injected both by the member injector ofFooChild
AND by the member injector ofFoo
. - this mechanism takes into account the overrides of methods: an injected method that overrides an injected method will only be called once by Toothpick, it is the responsibility of the developer to consider invoking the super version of this method or not.
- inheritance is also supported across modules.
Once Toothpick generates the factories and member injectors, it will need to use them at runtime. This is the only part of TP that "uses reflection", and it's a common practice: ButterKnife, Dagger, etc. all do this.