Skip to content

Factories and Member Injectors

Stéphane Nicolas edited this page Jul 17, 2019 · 21 revisions

Factories

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.

When does Toothpick create a Factory ?

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();
  }
}

Classes with @Inject annotated members (fields or methods)

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

Constructors with parameters

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

Scope annotated class

If a class is annotated with a scope annotation such as

@Singleton
class Foo {
}

Then Toothpick will create a factory:

  • for the class Foo

Factories and scopes

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.

Member Injectors

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.

When does Toothpick create a MemberInjector ?

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);
  }
}

Factories & Member Injectors

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.

Member Injectors and inheritance

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 of Foo. Any instance of FooChild will be injected both by the member injector of FooChild AND by the member injector of Foo.
  • 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.

Factories & Member Injectors lookup

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.

Links

Clone this wiki locally