-
Notifications
You must be signed in to change notification settings - Fork 43
Home
A Motif Scope is the top-level container for all other Motif features.
@motif.Scope
interface FooScope {}
- Interface annotated with
@motif.Scope
- Must be an interface
- Analogous to Dagger Component or Subcomponent
An access method allows access to objects on the dependency graph defined by the Scope.
@motif.Scope
interface FooScope {
Bar bar();
...
}
- Analogous to Dagger Component provision method
Child methods declare Scopes as child Scopes of other Scopes.
@motif.Scope
interface ScopeA {
ScopeB b();
}
@motif.Scope
interface ScopeB {
ScopeC c();
}
@motif.Scope
interface ScopeC {}
In the example above...
ScopeA -> ScopeB -> ScopeC
-
ScopeB
is a child Scope ofScopeA
-
ScopeC
is a child Scope ofScopeB
-
ScopeC
is a descendant Scope ofScopeA
-
ScopeA
is a parent Scope ofScopeB
-
ScopeB
is a parent Scope ofScopeC
-
ScopeA
is an ancestor Scope ofScopeC
Any dependencies provided by an @Expose-annotated factory method are automatically available to child and descendant Scopes. See @Expose for details.
A child method allows the creation of child Scopes.
@motif.Scope
interface FooScope {
BarScope bar();
BazScope baz(SomeDependency d);
...
}
- Declared on top-level Scope interface
- Returns a type that is itself a Scope
- Parameters define dynamic dependencies
- Analogous to Dagger Subcomponent factory method.
Dynamic dependencies allow contributions to the Motif DI graph for dependencies that can't be statically provided by the parent or child Scope. The canonical example is passing an AuthToken
retrieved from the network to a child Scope. The AuthToken
in the example below is made available to LoggedInScope
.
@motif.Scope
interface OnboardingScope {
LoggedInScope loggedIn(AuthToken authToken);
...
}
- Defined as parameters on child methods
- Multiple parameters allowed
- Dynamic dependencies are available to the immediate child Scope
- Dynamic dependencies are NOT transitively available to grand-child Scopes or further descendants
Similar behavior can be accomplished with Dagger's @BindsInstance or passing dependencies to a @Module constructor.
A @motif.Objects
-annotated class nested in a Motif Scope interface defines Motif factory methods.
@motif.Scope
interface FooScope {
@motif.Objects
class Objects {}
}
- Defined as a nested class of a Scope interface
- Declares factory-methods
- May not define any non-static fields
- May not define any constructors
- Analogous to Dagger @Modules
Factory methods tell Motif how to construct dependencies.
@motif.Scope
interface FooScope {
@motif.Objects
class Objects {
Foo foo(Bar bar) {
return new Foo(bar);
}
Bar bar() {
return new Bar();
}
}
}
- A provided dependency is the type instantiable by a factory method
- Required dependencies are the types required by a factory method in order to instantiate the provided dependency
- A Factory method can be one of three types: Basic, Constructor, or Binds
- A provided dependency is defined by the return type of the factory method
- Required dependencies are defined differently based on the type of the factory method
- A factory method is valid if all required dependencies are provided either by factory methods defined on the same Scope or by @Expose-annotated factory methods defined on ancestor scopes.
- A factory method must not return void.
- Analogous to Dagger @Module methods
Motif invokes basic factory methods directly in order to instantiate the provided dependency.
@motif.Scope
interface FooScope {
@motif.Objects
class Objects {
Foo foo(Bar bar) {
return new Foo(bar);
}
Bar bar() {
return new Bar();
}
}
}
- Method must NOT be
abstract
- Required dependencies are defined by method parameters
- Analogous to Dagger @Provides method
Constructor factory methods tell Motif to use the constructor directly to instantiate the provided dependency.
@motif.Scope
interface FooScope {
@motif.Objects
abstract class Objects {
abstract Foo foo();
abstract Bar bar();
}
}
class Foo {
@javax.Inject
Foo(Bar bar) {...}
Foo() {...}
}
class Bar {}
- Method must be
abstract
- Method must be parameterless
- Required dependencies are defined by provided dependency constructor parameters
- If the provided dependency defines multiple constructors...
- Exactly one constructor must be annotated with
@javax.Inject
- Motif will use the
@javax.Inject
-annotated constructor for instantiation
- Exactly one constructor must be annotated with
- Analogous to Dagger constructor injection
Binds factory methods tell Motif to use an instance of the method parameter type to provide the provided dependency.
@motif.Scope
interface FooScope {
@motif.Objects
abstract class Objects {
abstract Foo foo(Bar bar); // Binds
abstract Bar bar(); // Constructor
}
}
interface Foo {}
class Bar implements Foo {}
- Method must be abstract
- Method must define exactly one parameter
- Parameter must be assignable to return type
- Parameter defines the single required dependency
- Analogous to Dagger @Binds methods
An @Exposed
-annotated factory method exposes the provided dependency to child and descendant Scopes.
@motif.Scope
interface ScopeA {
ScopeB b();
@motif.Objects
abstract class Objects {
// Bar can be consumed in descendants of ScopeA.
@motif.Expose
abstract Bar bar();
}
}
@motif.Scope
interface ScopeB {
@motif.Objects
abstract class Objects {
// Bar is provided by ScopeA
Foo foo(Bar bar) {
return new Foo(bar);
}
}
}
Dynamic dependencies are only exposed to the immediate child Scope by default. Annotate the child method parameter with @Expose to allow all descendant scopes to consume the dynamic dependency.
@motif.Scope
interface ScopeA {
// All descendants of ScopeB can consume AuthToken.
ScopeB b(@Expose AuthToken authToken);
}
- A dependency declared in a parent Scope can only be consumed in a child or descendant scope if the providing factory method in the parent is annotated with
@Expose
- Descendants of the immediate child Scope can consume a dynamic dependency only if the child method parameter is annotated with
@Expose
. -
@Expose
automatically exposes the provided dependency to all descendants (not just the immediate child)
- Dagger's @Subcomponents behave differently in that all dependencies provided by a Subcomponent are available to children and descendants by default.
@Spread
is best explained by example. In the code snippet below, Bar
receives its String
dependency from Foo
's Foo.string()
method and its Integer
dependency from Foo.integer()
.
public class Foo {
public String string() {
return "s";
}
public Integer integer() {
return 1;
}
}
@motif.Scope
interface FooScope {
@motif.Objects
class Objects {
@motif.Spread
Foo foo() {
return new Foo();
}
// String is provided by Foo.string()
// Integer is provided by Foo.integer()
Bar bar(Foo foo, String string, Integer integer) {
return new Bar(foo, string, integer);
}
}
}
@Spread
-annotated methods provide any types returned by public, void, parameterless methods declared by the factory method's provided dependency.
-
@Spread
-annotated factory methods provide the return type and types returned by any spreadable methods declared by the return type. - A method is spreadable if it is public, non-void, and parameterless.
Objects provided by factory methods are cached by default. This means that the same instance will be provided anywhere a given type is requested. To opt out of this behavior, annotate the providing factory method with @DoNotCache
.
Explicitly declares the dependencies of a given Scope.
@Scope
interface FooScope extends Creatable<FooDependencies> {}
interface FooDependencies {
Bar bar();
}
FooScope fooScope = ScopeFactory.create(FooScope.class, fooDependencies);
Motif offers two ways to provide dependencies to child Scopes. Here are some notes on how they differ and when it's appropriate to use each.
Annotating a factory method with @Expose
allows all descendant Scopes to consume the provided dependency. This is useful for dependencies that are consumed further down the graph, perhaps in multiple different descendant Scopes. If you want to restrict access to the dependency to just the immediate child scope, you may want to use dynamic dependencies.
Dynamic dependencies are dependencies that are passed as parameters into child methods. These dependencies are only available to the immediate child Scope by default. This strategy is appropriate when the dependency can't be provided via a factory method or when you want to restrict access to the dependency to just the immediate child Scope. In the former case, you can still opt-in to allowing access to all descendants by annotating the parameter with @Expose
.
Motif has a few different code generation modes:
Java
- This is the default for Java Motif code
- Explicitly enable with
-Amotif.mode=java
- Generates a pure Java implementation
Kotlin
- This is the default for Kotlin Motif code
- Explicitly enable with
-Amotif.mode=kotlin
- Generates a pure Kotlin implementation
- Avoids mixed source-sets
Dagger
- Warning: This is not recommended and may be removed in the future
- Must be enabled explicitly:
-Amotif.mode=dagger