Skip to content
Leland Takamine edited this page Feb 6, 2020 · 40 revisions

@Scope

A Motif Scope is the top-level container for all other Motif features.

@motif.Scope
interface FooScope {}
Spec
  • Interface annotated with @motif.Scope
  • Must be an interface
Dagger Notes

Access Methods

An access method allows access to objects on the dependency graph defined by the Scope.

@motif.Scope
interface FooScope {
  Bar bar();
  ...
}
Spec
  • Declared on top-level Scope interface
  • Parameterless
  • Returns any type that is not a Scope
Dagger Notes

Child Scopes

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
Terminology
  • ScopeB is a child Scope of ScopeA
  • ScopeC is a child Scope of ScopeB
  • ScopeC is a descendant Scope of ScopeA
  • ScopeA is a parent Scope of ScopeB
  • ScopeB is a parent Scope of ScopeC
  • ScopeA is an ancestor Scope of ScopeC
Behavior

Any dependencies provided by an @Expose-annotated factory method are automatically available to child and descendant Scopes. See @Expose for details.

Child Methods

A child method allows the creation of child Scopes.

@motif.Scope
interface FooScope {
  BarScope bar();
  BazScope baz(SomeDependency d);
  ...
}
Spec
Dagger Notes

Dynamic Dependencies

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);
  ...
}
Spec
  • 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
Dagger Notes

Similar behavior can be accomplished with Dagger's @BindsInstance or passing dependencies to a @Module constructor.

Related

@Objects

A @motif.Objects-annotated class nested in a Motif Scope interface defines Motif factory methods.

@motif.Scope
interface FooScope {

  @motif.Objects
  class Objects {}
}
Spec
  • Defined as a nested class of a Scope interface
  • Declares factory-methods
  • May not define any non-static fields
  • May not define any constructors
Dagger Notes

Factory Methods

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();
    }
  }
}
Terminology
  • 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
Spec
  • 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.
Dagger Notes
  • Analogous to Dagger @Module methods

Basic

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();
    }
  }
}
Spec
  • Method must NOT be abstract
  • Required dependencies are defined by method parameters
Dagger Notes

Constructor

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 {}
Spec
  • 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
Dagger Notes
  • Analogous to Dagger constructor injection

Binds

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 {}
Spec
  • Method must be abstract
  • Method must define exactly one parameter
  • Parameter must be assignable to return type
  • Parameter defines the single required dependency
Dagger Notes
  • Analogous to Dagger @Binds methods

@Expose

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);
}
Spec
  • 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 Notes
  • Dagger's @Subcomponents behave differently in that all dependencies provided by a Subcomponent are available to children and descendants by default.
Related

@Spread

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

Spec
  • @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.

@DoNotCache

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.

Creatable / ScopeFactory

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

@Expose vs Dynamic Dependencies

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.

Code Generation Mode

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