Metro Debugging
Debug and troubleshoot Metro dependency injection issues. This covers compile-time errors, graph visualization, performance analysis, and common problems.
Debug Logging
Enable verbose debug output to see generated code structure:
metro {
debug = true
}
This prints pseudocode for generated IR classes during compilation.
Graph Reports and Visualization
Generate Reports
Configure report output:
metro {
reportsDestination = layout.buildDirectory.dir("reports/metro")
}
Generate and View
# Generate graph metadata
./gradlew :app:generateMetroGraphMetadata
# Analyze and produce metrics
./gradlew :app:analyzeMetroGraph
# Generate interactive HTML visualization
./gradlew :app:generateMetroGraphHtml
# Opens automatically in browser
Understanding the Visualization
Node Colors (binding types):
- Blue: Constructor-injected (
@Inject) - Green: Provided (
@Provides) - Gray: Alias (
@Binds) - Light Blue: Bound instance
- Pink: Multibinding
- Purple: Graph extension
- Peach: Assisted injection
Node Shapes:
- Diamond: Main
@DependencyGraph - Rounded rectangle:
@GraphExtension - Magenta border circle: Scoped binding (
@SingleIn) - Regular circle: Normal binding
Edge Styles:
- Gray solid: Normal dependency
- Light blue thick: Accessor (entry point)
- Magenta dashed: Inherited binding
- Cyan dashed: Deferrable (
Provider/Lazy) - Orange thick: Assisted injection
- Purple: Multibinding contribution
- Gray dotted: Alias
Glow Effects (metrics):
- Red glow: High betweenness centrality (critical path)
- Yellow glow: Moderate centrality
- Blue glow: High fan-in (many dependents)
Common Compile-Time Errors
Missing Binding
Error:
[Metro/MissingBinding] Cannot find @Inject constructor or @Provides for: kotlin.Int
kotlin.Int is requested at
[test.ExampleGraph] test.ExampleGraph.int
Similar bindings:
- @Named("qualified") Int (Different qualifier)
- Number (Supertype)
Solutions:
-
Add
@Injectto the class constructor:@Inject class MyClass(...) -
Add a
@Providesfunction:@Provides fun provideInt(): Int = 42 -
Check qualifier mismatch - the error shows similar bindings with different qualifiers
-
For multi-module: use
@ContributesBindingin the implementation module
Dependency Cycle
Error:
[Metro/DependencyCycle] Found a dependency cycle:
kotlin.Int → kotlin.String → kotlin.Double → kotlin.Int
Solutions:
-
Use
Provider<T>to defer one dependency:@Inject class A(private val bProvider: Provider<B>) { fun useB() = bProvider() } -
Use
Lazy<T>for lazy initialization:@Inject class A(private val b: Lazy<B>) { fun useB() = b.value } -
Restructure dependencies to break the cycle
Scope Mismatch
Error:
[Metro/ScopeMismatch] Scoped binding 'Database' with scope 'AppScope'
cannot be used in unscoped graph 'FeatureGraph'
Solutions:
-
Add the scope to the graph:
@DependencyGraph(AppScope::class) interface FeatureGraph -
Remove the scope from the binding if it shouldn't be scoped
-
Include the scoped graph as a dependency:
@DependencyGraph.Factory interface Factory { fun create(@Includes appGraph: AppGraph): FeatureGraph }
Duplicate Binding
Error:
[Metro/DuplicateBinding] Multiple bindings found for: Repository
- RepositoryImpl via @ContributesBinding
- AnotherRepositoryImpl via @ContributesBinding
Solutions:
-
Use
replacesto explicitly choose one:@ContributesBinding(AppScope::class, replaces = [AnotherRepositoryImpl::class]) class RepositoryImpl : Repository -
Use qualifiers to distinguish them:
@Named("primary") @ContributesBinding(AppScope::class) class RepositoryImpl : Repository -
Use
excludesin the graph:@DependencyGraph(AppScope::class, excludes = [AnotherRepositoryImpl::class]) interface AppGraph
Unmatched Exclusions/Replacements
When reportsDestination is set, check these files:
merging-unmatched-exclusions-*.txtmerging-unmatched-replacements-*.txt
These indicate excludes or replaces targeting non-existent contributions.
Runtime Debugging
Dynamic Graphs for Testing
Replace bindings at runtime:
@DependencyGraph
interface AppGraph {
val repository: Repository
@Provides fun provideRepository(): Repository = RealRepository()
}
@BindingContainer
object FakeBindings {
@Provides fun provideRepository(): Repository = FakeRepository()
}
class AppTest {
@Test
fun testWithFake() {
val graph = createDynamicGraph<AppGraph>(FakeBindings)
// graph.repository now returns FakeRepository
assertIs<FakeRepository>(graph.repository)
}
}
Inspecting Generated Code
For JVM targets, decompile generated code:
- Navigate to build output:
build/classes/kotlin/main/ - Find the generated graph implementation (e.g.,
Metro_AppGraph.class) - Use IntelliJ's "Decompile to Java" action
For Android: check build/tmp/kotlin-classes/debug/
Performance Debugging
Compilation Tracing
Generate Perfetto traces:
metro {
traceDestination = layout.buildDirectory.dir("metro/trace")
}
Build with cache bypass:
./gradlew :app:compileKotlin --rerun
Load the trace in Perfetto UI.
Build Performance Issues
Slow incremental builds:
- Enable graph sharding for large graphs:
metro { enableGraphSharding = true keysPerGraphShard = 2000 }
Large method errors (JVM):
- Enable chunking (default):
metro { chunkFieldInits = true statementsPerInitFun = 25 }
Slow initialization:
- Enable switching providers for deferred class loading:
metro { enableSwitchingProviders = true }
Validation Commands
Validate Graph Structure
# Full build with validation
./gradlew :app:compileKotlin
# Check for unused bindings (enabled by default)
# Warnings appear if shrinkUnusedBindings detects unreachable bindings
Check Contributions
Verify that contributions are being aggregated:
- Enable debug mode temporarily
- Check that
@ContributesTointerfaces appear in generated graph - Verify scope markers match between contributions and graph
Troubleshooting Checklist
Binding Not Found
- Class has
@Injecton constructor? -
@Providesfunction exists? - Qualifiers match exactly?
- Scope is accessible from the graph?
- Multi-module:
@ContributesBindinghas correct scope?
Graph Not Generating
- Metro plugin applied in build.gradle.kts?
- Kotlin version is 2.2.20+?
-
metro.enabledis not set tofalse? - Interface/class has
@DependencyGraphannotation?
Contributions Not Merging
- Scope markers match?
- Plugin applied to contributing module?
- No typos in scope class references?
- Contributing module is a dependency of the graph module?
IDE Not Showing Generated Code
- K2 mode enabled?
-
kotlin.k2.only.bundled.compiler.plugins.enabledset tofalse? - Project rebuilt after changes?
Diagnostic Configuration
Full diagnostic setup:
metro {
debug = true
reportsDestination = layout.buildDirectory.dir("reports/metro")
traceDestination = layout.buildDirectory.dir("metro/trace")
}
Then inspect:
- Build output for debug logs
build/reports/metro/for graph reportsbuild/metro/trace/for performance traces
Getting Help
If issues persist:
- Check the Metro documentation
- Search GitHub issues
- Create a minimal reproduction case
- Enable
debug = trueand include relevant output
