Dependency Graphs¶
The primary entry points in Metro are dependency graphs. These are interfaces annotated with @DependencyGraph
and created with @DependencyGraph.Factory
interfaces. Graphs expose types from the object graph via accessor properties or functions.
Tip
These are synonymous with components and @Component
/@Component.Factory
in Dagger and kotlin-inject.
Tip
“Accessors” in Metro are synonymous with Dagger’s provision methods.
Accessors and member injections act as roots, from which the dependency graph is resolved. Dependencies can be provided via conventional @Provides
functions in graphs or their supertypes, constructor-injected classes, or accessed from graph dependencies.
@DependencyGraph
interface AppGraph {
val message: String
@Provides
fun provideMessage(): String = "Hello, world!"
}
Note the @Provides
function must define an explicit return type.
Simple graphs like this can be created via the createGraph()
intrinsic.
val graph = createGraph<AppGraph>()
Graphs are relatively cheap and should be used freely.
Inputs¶
Instance parameters and graph dependencies can be provided via a @DependencyGraph.Factory
interface that returns the target graph.
@DependencyGraph
interface AppGraph {
val message: String
@DependencyGraph.Factory
fun interface Factory {
fun create(@Provides message: String): AppGraph
}
}
@DependencyGraph
interface AppGraph {
val message: String
@DependencyGraph.Factory
fun interface Factory {
fun create(messageGraph: MessageGraph): AppGraph
}
@DependencyGraph interface MessageGraph {
val message: String
@Provides fun provideMessage(): String = "Hello, world!"
}
}
Like Dagger, non- @Provides
instance dependencies can be any type. Metro will treat accessor candidates of these types as usable dependencies.
@DependencyGraph
interface AppGraph {
val message: String
@DependencyGraph.Factory
fun interface Factory {
fun create(messageProvider: MessageProvider): AppGraph
}
interface MessageProvider {
val message: String
}
}
Graph factories can be created with the createGraphFactory()
intrinsic.
val messageGraph =
createGraphFactory<AppGraph.Factory>()
.create("Hello, world!")
Scoping¶
See Scopes for more details on scopes!
Graphs may declare a scope
(and optionally additionalScopes
if there are more). Each of these declared scopes act as an implicit @SingleIn
representation of that scope for aggregation.
For example:
@DependencyGraph(AppScope::class)
interface AppGraph
Is functionally equivalent to writing the below.
@SingleIn(AppScope::class)
@DependencyGraph(AppScope::class)
interface AppGraph
Implementation Notes¶
Dependency graph code gen is designed to largely match how Dagger components are generated.
- Dependencies are traversed from public accessors and
inject()
functions. - Metro generates Provider Factory classes for each provider. These should be generated at the same time that the provider is compiled so that their factory classes. This is for two primary purposes:
- They can be reused to avoid code duplication
- Metro can copy default values for provider values over to the generated factory to support optional bindings. Since default values may refer to private references, we must generate these factories as nested classes.
- Metro generates a graph impl class that holds all aggregated bindings and manages scoping.
- Scoped bindings are stored in provider fields backed by
DoubleCheck
. - Reused unscoped providers instances are stored in reusable fields.
@Provides
factory parameters are stored in a field backed byInstanceFactory
.- Multibindings create new collection instances every time.
- Multibinding providers are not accessible as standalone bindings.