Library / SDK
uber/motif avatar
uber/motif

Motif is a dependency injection library that treats scope nesting as the primary case

A simple DI API for Android / Java

537 stars46 forksKotlinApache-2.0

At a glance

What is it?
Motif is an Uber dependency injection library for Android and Java whose API is built around nested scopes, with an explicit Expose annotation controlling what a child scope can see and a compile time report of missing parent dependencies. It suits a team with deep scope hierarchies who wants less DI configuration to reason about, and it gives up flexibility relative to Dagger.
Who is it for?
Motif fits an Android team whose dependency graph is a deep hierarchy of scopes and who finds Dagger's configuration overhead the main cost, since Motif's scope model, the single Objects class per scope and the Expose annotation cut most of that. It does not fit a project that needs the full range of Dagger features, because the README says Dagger can express everything Motif can and much more.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Kotlin, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The scope is the unit of design

Motif is a DI library that offers a simple API optimized for nested scopes, and the first thing the README notes is a change in how it works: past versions of Motif generated Dagger code under the hood, and this is no longer the case.

That line matters for anyone arriving from older documentation, because the compiler, the generated code and the Dagger interop story all changed. There is now a DAGGER.md in the repository covering interoperability, which is the file to read if you are moving an existing graph across.

The core concept is a Scope, which serves as a container for objects that can be created by that Scope.

java
@motif.Scope
interface MainScope {}

For Dagger users the README provides the analogy directly: a Motif Scope is analogous to a Dagger `@Component`.

The other resources the README lists tell you what kind of project this is. There is the Dagger interoperability document, an IDE integration directory under intellij/, and a DroidCon talk.

The library is Apache-2.0 licensed with LICENSE.txt at the root, written in Kotlin, and the repository has no GitHub releases. The last push was on 2026-10-16, and the default branch is main.

The emphasis on nested scopes is the whole thesis. Most DI libraries are designed for a flat graph with optional substructure. Motif is designed for the case where your hierarchy is the design, and the API is shaped around making that case cheap to write and cheap to read.

Factories, access methods, and one Objects class per scope

Three pieces make up a scope: an `@motif.Objects` annotated class holding factory methods, dependencies passed as factory method parameters, and access methods on the scope interface for retrieving objects.

The factory methods tell Motif how to create objects. For Dagger users the README says the nested Objects class is just like a Dagger `@Module` except that Motif only allows you to define one Objects class per Scope, and the factory methods are analogous to `@Provides` methods.

java
@motif.Scope
interface MainScope {

    @motif.Objects
    class Objects {

        Controller controller() {
            return new Controller();
        }
    }
}

Dependencies are ordinary method parameters, and Motif has to know how to create those too.

java
@motif.Scope
interface MainScope {

    @motif.Objects
    class Objects {

        View view() {
            return new View();
        }
        
        Database database() {
            return new Database();
        }

        Controller controller(View view, Database database) {
            return new Controller(view, database);
        }
    }
}

The access methods are how you get objects back out. The README compares them to Dagger's `@Component` provision methods.

java
@motif.Scope
interface MainScope {

    Controller controller();

    @motif.Objects
    class Objects {

        View view() {
            return new View();
        }
        
        Database database() {
            return new Database();
        }

        Controller controller(View view, Database database) {
            return new Controller(view, database);
        }
    }
}

At build time Motif generates an implementation class for each scope, so the code you write against is an interface and the generated class supplies the wiring.

java
MainScope mainScope = new MainScopeImpl();
Controller controller = mainScope.controller();

The one-Objects-class-per-scope rule is the first place Motif is more opinionated than Dagger, and it is a rule that buys something: there is exactly one place in each scope where construction happens, so a reader knows where to look.

Child scopes and the Expose annotation

A child scope is declared with a child method on the scope interface, which the README compares to a Dagger `@Subcomponent` factory method on a parent component.

java
@motif.Scope
interface MainScope {

    ChildScope child();
    
    // ...
}

The interesting part is what a child scope can see. A factory method annotated with `@Expose` becomes visible to child scopes, and the README contrasts this with Dagger, where `@Subcomponents` expose all objects down the graph by default, while Motif scopes consider objects internal to the Scope unless explicitly annotated otherwise.

java
@motif.Scope
interface MainScope {

    ChildScope child();

    // ...

    @motif.Objects
    class Objects {

        @Expose
        Database database() {
            return new Database();
        }

        // ...
    }
}

@motif.Scope
interface ChildScope {

    ChildController controller();

    @motif.Objects
    class Objects {

        // No Database factory method. Child Controller receives the Database defined by MainScope.

        ChildView view() {
            return new ChildView();
        }

        ChildController controller(Database database, ChildView view) {
            return new ChildController(database, view);
        }
    }
}

So the child scope has no Database factory of its own and simply receives the Database defined by MainScope, because that one was exposed.

The default being closed is a real difference in behaviour from Dagger, and it is a safety property. In Dagger a subcomponent sees everything its parent can provide, so the parent's surface grows silently as you add factory methods. In Motif, adding a factory method to a parent scope does not change what children can see, which means the coupling between scopes is visible in the source rather than implied by the hierarchy.

You create a child scope by calling the parent's child method.

java
MainScope mainScope = new MainScopeImpl();
ChildScope childScope = mainScope.child();

The child method is also where the parent decides which child implementation it wants, which is the flexibility point in the design.

Root scopes, Creatable, and catching missing dependencies early

A root scope is a scope with no parent, and it has an extra step that the nested scopes do not need: you have to say what it expects from the outside.

By extending `Creatable<D>` you can specify exactly the dependencies you expect from the parent Scope, and the point of that is that Motif can report missing dependencies at compile time.

java
@motif.Scope
interface MainScope extends Creatable<MainDependencies> {

    // ...
}

interface MainDependencies {}

Extending Creatable also enables instantiation of a root Scope without referencing generated code, using the ScopeFactory.create API.

java
MainDependencies dependencies = ...;
MainScope mainScope = ScopeFactory.create(MainScope.class, dependencies)

The compile time check is the feature worth having. A root scope is where you inject things that come from outside the graph, and getting the declared set wrong is the classic root-scope bug. With Dagger this typically surfaces as a runtime failure or as a graph that resolves something you did not intend. Here the compiler tells you.

The two-step design is also how you keep the generated class out of your code: ScopeFactory.create takes the interface and the dependency object, so a test can construct a scope with fakes without touching MainScopeImpl.

Convenience APIs for constructor injection, generics and inheritance

There is a shortcut for the common case where a factory method just passes parameters through to a constructor without modification. Such methods can be written as parameterless abstract methods.

java
@motif.Scope
interface MainScope {

    // ...

    @motif.Objects
    abstract class Objects {
        abstract View view();
        abstract Database database();
        abstract Controller controller();
    }
}

The README compares this to Dagger's `@Inject` constructor injection, with the difference that it only requires annotating the class constructor if there are multiple constructors, and it scopes the object to the enclosing Motif Scope.

That is a real ergonomic difference. Dagger needs an annotation on the constructor or on the class to know it is injectable. Motif infers the constructor from the abstract method's return type, and the scope relationship comes from where the abstract method is declared.

Motif also handles inheritance and generics.

java
interface ControllerObjects<C, V> {
    V view();
    C controller();
}

@motif.Scope
interface MainScope {

    // ...

    @motif.Objects
    abstract class Objects implements ControllerObjects<Controller, View> {
        abstract Database database();
    }
}

The generics case matters for a scope hierarchy where a controller and its view are parameterised types that differ per level. A shared interface declares the shape once and each scope's Objects class binds the type arguments.

The convenience APIs are all optional. A scope written with explicit factory methods and explicit access methods is the plain form, and everything above is a way to write less when the shape allows it.

Building it, and the ProGuard rules

Motif ships as two Maven Central artifacts, the compiler and the runtime, and the Gradle setup has one line for each.

The two lines are `annotationProcessor 'com.uber.motif:motif-compiler:x.y.z'` and `implementation 'com.uber.motif:motif:x.y.z'`. The compiler is an annotation processor, which is where the scope implementations come from.

If you shrink your build, there are four keep rules to add.

proguard
-keep class motif.Scope
-keep class motif.ScopeImpl
-keep @motif.Scope interface *
-keep @motif.ScopeImpl class *

Both the annotation and the generated implementation class have to survive shrinking, and the annotation-driven rules handle the ones that are only referenced reflectively.

The repository layout shows how much of this is a compiler project rather than just a library. There is a compiler/ directory, an ast/ directory, models/, xprocessing/ and xprocessing-testing/, errormessage/, a core/ directory and a lib/ directory, plus config/ for the Gradle configuration. There is a viewmodel/ directory, which suggests a companion piece for ViewModel scoping, and intellij/ for the IDE integration the README links.

The samples directory is where the practical differences show. There is samples/sample/, samples/sample-kotlin/ and samples/sample-kotlin-ksp/, the Kotlin equivalents samples/sample-lib/ and samples/sample-lib-ksp/, and samples/dagger-comparison/ which is the side-by-side with Dagger.

Those KSP samples are worth noting. Motif used to generate Dagger code, and the presence of both a plain Kotlin and a KSP sample suggests the annotation processing path and the Kotlin Symbol Processing path are both supported, which matters if you are on a modern Kotlin setup and do not want to run the Java annotation processor.

Where Motif is the wrong choice

Four cases argue against it.

The first is a flat or shallow graph. Motif sacrifices flexibility in favour of an opinionated API optimized specifically for deep DI scope hierarchies, and the README says the aim is to minimise initial development cost and continued conceptual overhead attributed to DI configuration. If your graph is a dozen objects with no hierarchy, the opinionated parts cost you and the hierarchy buys you nothing.

The second is a need for the full Dagger feature set. The README says Dagger can express everything Motif can and much more. Qualifier support, multibindings, full component dependencies and the wider ecosystem of Dagger extensions are not in Motif, and if you need one of them there is no partial adoption.

The third is an existing Dagger codebase. DAGGER.md exists, but the README notes that past versions of Motif generated Dagger code under the hood and no longer do, so a migration is a real change of mechanism rather than a swap of annotations. The Interop document is the place to find out how much of your graph translates.

The fourth is a team that does not want a compile step or an annotation processor. Motif is a compiler-based DI library in the Dagger tradition, so you need the processor on the annotation processor path or the KSP path, and the generated class names are part of the contract.

The alternative in the same direction is Dagger itself, which the README treats as the reference point throughout. The difference in approach is that Dagger gives you a general dependency injection system with modules, components, qualifiers and a large ecosystem of extensions, while Motif gives you a smaller vocabulary built around scopes and their nesting, with the trade deliberately made in favour of fewer concepts. For an application whose screens map onto a deep scope tree, which is common in Android, Motif is less configuration for the same graph. For a library or a framework that needs to express arbitrary graphs, Dagger is the more capable tool.

Editorial conclusion

Motif fits an Android team whose dependency graph is a deep hierarchy of scopes and who finds Dagger's configuration overhead the main cost, since Motif's scope model, the single Objects class per scope and the Expose annotation cut most of that. It does not fit a project that needs the full range of Dagger features, because the README says Dagger can express everything Motif can and much more. Add `annotationProcessor 'com.uber.motif:motif-compiler:x.y.z'` with `implementation 'com.uber.motif:motif:x.y.z'`, keep the four ProGuard rules for `motif.Scope`, `motif.ScopeImpl` and the annotated types, and read DAGGER.md before migrating an existing Dagger graph.

Frequently asked questions

What is Motif?

Motif is a dependency injection library for Android and Java that offers a simple API optimized for nested scopes. It compiles scope implementations with its own compiler, and the README notes that past versions generated Dagger code under the hood, which is no longer the case.

How do I add Motif to an Android project?

With two Gradle lines, the compiler as an annotationProcessor and the runtime as an implementation: `annotationProcessor 'com.uber.motif:motif-compiler:x.y.z'` and `implementation 'com.uber.motif:motif:x.y.z'`. Both artifacts are on Maven Central under the com.uber.motif group.

What does the Expose annotation do in Motif?

It makes a factory method visible to child scopes. Unlike Dagger subcomponents, which expose all objects down the graph by default, Motif scopes treat objects as internal to the Scope unless explicitly annotated with @Expose, so coupling between scopes is visible in the source.

What ProGuard rules does Motif need?

Four keep rules: `-keep class motif.Scope`, `-keep class motif.ScopeImpl`, `-keep @motif.Scope interface *` and `-keep @motif.ScopeImpl class *`. Both the annotation and the generated implementation have to survive shrinking.

How is Motif different from Dagger?

Motif is deliberately opinionated around deep scope hierarchies, with one Objects class per scope and closed-by-default visibility between scopes, while Dagger can express everything Motif can and much more. The repository ships a DAGGER.md interoperability document and a samples/dagger-comparison project.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. uber/motif on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/uber-motif.svg)](https://hysenlabs.com/projects/uber-motif)

Was this useful?