Skip to content

Factories and Member Injectors

Daniel Molinero edited this page Jun 28, 2016 · 21 revisions

Factories

In Toothpick, a Factory<Foo> is a class that produces instance of Foo.

As Toothpick does not rely on reflection at all, with the idea in mind that 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();
  }
}

Optimistic Factory creation

As opposed to compile-time DI libraries, Toothpick bindings are defined at runtime. Thus, Toothpick cannot know at compilation time wether or not a class will actually be instantiated via DI.

class Bar { 
  @Inject IFoo iFoo;
}

When Toothpick annotation processors compile class Bar, they can't know whether or not a binding will be defined at runtime for IFoo. 2 scenarios are possible: either a binding will be defined such as:

//binding information is available at runtime
new Module() {{
 bind(IFoo.class).to(Foo.class); 
}};

OR no binding will be defined at all. This case is perfectly appropriate, it implies that IFoo --> IFoo (IFoo would be bound to itself). This case is common, as it allows to use DI in a general way, and makes unit testing easier.

Toothpick doesn't know what will happen at runtime, but it tries to help developers. When compiling Bar, it will try to create a factory to IFoo, which means that the developer doesn't have anything to do to make IFoo instantiable by DI. No need for an @Inject annotated constructor, Toothpick will act as if there was one in the class IFoo. This is what we call "Optimistic Factory Creation".

Optimistic factory creation is the creation of (normal) factory classes, but in an optimistic way: Toothpick hopes that we have all information at compile time and that no binding will be defined at runtime. Toothpick will optimistically try to create a Factory for classes that are not explicitely marked as instantiable via DI.

Of course, when Toothpick creates an optimistic factory for a class, the class needs to be instantiable (i.e. non private, non abstract classes, if it's an inner class, it must be static).

Those classes must have either:

  • an annotated constructor --> it will be used to create instances via the factory
  • no constructors --> the default constructor will be used to create instances via the factory
  • a default constructor --> it will be used to create instances via the factory

The constructor must be at least package visible. If no optimistic factory creation is possible, Toothpick will not fail or even emit a warning, it understands that a binding will be defined at runtime, and it will then know which factory to use to create instances of IFoo when instantiating Bar.

You can turn off optimistic factory creation using Toothpick annotation processor options. We didn't provide a flag to turn off optimistic factory creation per say, and this could make sense as it will lead to a significant increase in the number of generated classes. Please, fill an issue or even better submit a PR if such feature happens to be needed in your case.

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 (optimistic)

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 (non optimistic factory, this is the normal case)

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 (optimistic)

Factories and scopes

When a class is annotated with a scope annotation, the generated factory will be scoped.

Example:

@ActivitySingleton
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(ActivitySingleton.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.

Also, the 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.

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 Member Injector ?

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.

Factories & Member Injectors lookup

Once Toothpick will generate the factories and member injectors, it will need to use them at runtime. Using default configurations, Toothpick will use reflection to load factories and member injectors.

Nevertheless, Toothpick is able to work without using reflection at all. In this case, it cannot rely on Class.forName("Foo$$Factory") to find the factory class. Toothpick will then use registries to locate both the factories and member injectors.

Links