- Scope Hierarchy
- Graph Extensions
- Binding Containers
- Qualifiers
- Assisted Injection
- Graph Creation
- Testing
Metro is the compile-time dependency injection compiler plugin used across the project. Every binding resolves at graph processing time without reflection. Each navigation level has a Metro scope that provides a ComponentContext to its children.
Scopes align with Decompose component lifecycles. Five scopes nest from the application down to inner pagers.
graph TD
subgraph AS["AppScope"]
direction LR
R["Repositories, Stores, Clients, Database"]
end
subgraph ACS["ActivityScope"]
direction LR
RN["Navigator"]
RP["Root presenter"]
NAV["Set<NavDestination<*>> + serializer registries"]
end
subgraph SS["ScreenScope (one per root child)"]
direction LR
HP["Tab host presenter"]
SP["Standalone screen presenters"]
SDP["Parameterized presenter factories"]
end
subgraph TS["TabScope (one per host tab)"]
direction LR
DIS["Tab presenters"]
end
subgraph PCS["Nested child scope"]
direction LR
UN["Inner pager presenters"]
end
AS ==> ACS
ACS ==> SS
SS ==> TS
TS ==> PCS
style AS fill:#FF9800,color:#fff,stroke:#E65100
style ACS fill:#4CAF50,color:#fff,stroke:#1B5E20
style SS fill:#2196F3,color:#fff,stroke:#0D47A1
style TS fill:#9C27B0,color:#fff,stroke:#4A148C
style PCS fill:#E91E63,color:#fff,stroke:#880E4F
AppScope: repositories, Stores, the database, Data Access Objects, Ktor clients,RequestManagerRepository, the datastore, logger, andLocalizer.ActivityScope: theNavigator, theRootPresenter, the unifiedSet<NavDestination<*>>, polymorphic serializer registries (Set<NavRouteBinding<*>>andSet<NavRootBinding<*>>), and*ScreenGraph.Factoryinstances emitted by the codegen processor.ScreenScope: tab host presenters, standalone screen presenters, and parameterized presenter factories. Created and destroyed alongside Decompose child stack entries.TabScope: tab presenters such as Discover, Library, and Search.- Nested child scope: inner pagers or secondary navigation inside a tab.
Tip
Use childContext(key) for children that must stay alive simultaneously. childStack destroys the previous child when a new one is pushed.
A scope descends from its parent through a @GraphExtension.Factory that accepts a Decompose ComponentContext. The codegen processor emits this shape for every annotated presenter (see navigation-codegen.md), including parent-owned children annotated with @ChildPresenter.
@Inject
@ChildPresenter(
scope = ProgressChildScope::class,
parentScope = ProgressRoot::class,
)
public class UpNextPresenter(componentContext: ComponentContext) : ComponentContext by componentContext
// Generated: <package>.di.UpNextChildGraph
@GraphExtension(ProgressChildScope::class)
public interface UpNextChildGraph {
public val upNextPresenter: UpNextPresenter
@ContributesTo(ProgressRoot::class)
@GraphExtension.Factory
public interface Factory {
public fun createUpNextGraph(
@Provides componentContext: ComponentContext,
): UpNextChildGraph
}
}The parent host (ProgressPresenter) consumes one factory per child and instantiates each through Decompose.childContext(key).
*Graph: a@DependencyGraphentry point or a scoped@GraphExtension.*BindingContainer: apublic objectthat groups@Providesmethods.ComponentandComponentContext: reserved for Decompose types.
Group related @Provides methods inside a @BindingContainer public object.
@BindingContainer
@ContributesTo(AppScope::class)
public object BaseBindingContainer {
@Provides @SingleIn(AppScope::class)
public fun provideDispatchers(): AppCoroutineDispatchers = ...
}- Prefer
@ContributesBindingon implementation classes. - Use a binding container for third-party types, qualified providers, or platform types.
A qualifier disambiguates identical types in the graph. Examples in the project:
@ApplicationContext: the AndroidContext.@TmdbApi,@TraktApi: the two split Ktor clients.@IoCoroutineScope,@MainCoroutineScope,@ComputationCoroutineScope.
@AssistedInject lets a presenter receive both injected dependencies and runtime parameters such as a show identifier.
@AssistedInject
public class ShowDetailsPresenter(
@Assisted private val param: ShowDetailsParam,
componentContext: ComponentContext,
// ...
) {
@AssistedFactory
public fun interface Factory {
public fun create(param: ShowDetailsParam): ShowDetailsPresenter
}
}- Android:
ApplicationGraphis created once inTvManicApplication.ActivityGraphextends it for each activity. - iOS:
IosApplicationGraphis created inAppDelegateand exposes factories for per-view graphs.
- Interfaces live in
data/*/api. - Implementations live in
data/*/implementationand are bound through@ContributesBinding. - Consumers, including presenters and interactors, depend on the
apimodule only.
Tests build custom graphs that swap production bindings for fakes.
FakeAppBindingContainer: contributes fakes such as a mock Ktor engine and feature stubs.testing/modules: each provides a fake implementation of the matchingapi/module.core/integration/ui: integration test scaffolding including the DSL, Robot, and Harness.