ContributesGraphExtension

@Target(allowedTargets = [AnnotationTarget.CLASS])
annotation class ContributesGraphExtension(val scope: KClass<*>, val isExtendable: Boolean = false)(source)

Generates a graph extension when the parent graph interface is merged.

The Problem

Imagine this module dependency tree:

          :app
/ \
v v
:login :user-data

:app defines the main dependency graph with @DependencyGraph. The :login module defines a graph extension for authenticated user flows, and :user-data provides some core functionality like UserRepository.

If :login defines its own graph directly with @DependencyGraph, it won't see contributions from :user-data unless :login depends on it directly.

The Solution

Instead, :login can use @ContributesGraphExtension(LoggedInScope::class) to say: "I want to contribute a new graph extension to the app graph." The extension will be generated in :app, which already depends on both :login and :user-data. Now UserRepository can be injected in LoggedInGraph.

@ContributesGraphExtension(LoggedInScope::class)
interface LoggedInGraph {

val userRepository: UserRepository

@ContributesGraphExtension.Factory(AppScope::class)
interface Factory {
fun createLoggedInGraph(): LoggedInGraph
}
}

In the :app module:

@DependencyGraph(AppScope::class, isExtendable = true)
interface AppGraph

The generated code will modify AppGraph to implement LoggedInGraph.Factory and implement createLoggedInGraph() using a generated final $$ContributedLoggedInGraph class that includes all contributed bindings, including UserRepository from :user-data.

// modifications generated during compile-time
interface AppGraph : LoggedInGraph.Factory {
override fun createLoggedInGraph(): LoggedInGraph {
return $$ContributedLoggedInGraph(this)
}

// Generated in IR
class LoggedInGraph$$MetroGraph(appGraph: AppGraph) : LoggedInGraph {
// ...
}
}

Finally, you can obtain a LoggedInGraph instance from AppGraph since it now implements LoggedInGraph.Factory:

// Using the asContribution() intrinsic
val loggedInGraph = appGraph.asContribution<LoggedInGraph.Factory>().createLoggedInGraph()

// Or if you have IDE support enabled
val loggedInGraph = appGraph.createLoggedInGraph()

Graph arguments

You can pass arguments to the graph via the factory:

@ContributesGraphExtension.Factory(AppScope::class)
interface Factory {
fun create(@Provides userId: String): LoggedInGraph
}

This maps to:

// Generated in IR
@DependencyGraph(LoggedInScope::class)
class $$ContributedLoggedInGraph(
@Extends parent: AppGraph,
@Provides userId: String
): LoggedInGraph {
// ...
}

In AppGraph, the generated factory method looks like:

// Generated in IR
override fun create(userId: String): LoggedInGraph {
return LoggedInGraph$$MetroGraph(this, userId)
}

Note: Abstract factory classes cannot be used as graph contributions.

Contributed graphs may also be chained, but note that isExtendable must be true to do so!

Types

Link copied to clipboard
annotation class Factory(val scope: KClass<*>)

A factory for the contributed graph extension.

Properties

Link copied to clipboard
val isExtendable: Boolean = false

If enabled, marks this graph as available for extension and generates extra metadata about this graph's available bindings for child graphs to read.

Link copied to clipboard
val scope: KClass<*>

The scope in which to include this contributed graph interface.