Troubleshooting
Most issues with Enro at runtime fall into one of a few categories — the controller isn’t installed, a destination isn’t bound to its key, a handle is accessed from the wrong scope, or a value can’t be serialized. This page walks through the common symptoms.
If you don’t find your issue here, please open an issue at github.com/isaac-udy/Enro/issues with a stack trace and a brief description of the call site.
Setup errors
EnroController has not been installed
The controller wasn’t installed before something tried to use it. Make sure you call installNavigationController(...) on your NavigationComponent before any composable that reads a navigation handle is composed.
// Android
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
MyComponent.installNavigationController(this) // <- this must run first
}
}
See Installation for the equivalent on each platform.
No NavigationHandle found for [KeyClass]
You called navigationHandle<MyKey>() from a composable that isn’t a navigation destination, or whose key is a different type.
A handle is only available inside a destination — that is, a composable function annotated with @NavigationDestination(...) (or the body of a navigationDestination<K> { } provider), or any composable composed inside one. The typed parameter must match the destination’s actual key type.
@Composable
@NavigationDestination(ShowProfile::class)
fun ProfileScreen() {
val navigation = navigationHandle<ShowProfile>() // âś… matches the annotation
// ...
}
@Composable
fun SomeRandomComposable() {
val navigation = navigationHandle<NavigationKey>() // ❌ not inside a destination
}
If your composable is genuinely shared between destinations and doesn’t need to know its key type, use the untyped form navigationHandle<NavigationKey>() and accept that navigation.key will be the base interface.
No LocalNavigationHandle
Same family as the previous error — composable reading the navigation handle from outside any destination. Wrap it inside a destination, or hoist the navigation calls up to a destination-level composable and pass the operations down as lambdas.
Missing navigation binding for [KeyClass]
Your key is being opened but no destination is registered for it. Three common causes:
- Forgot
@NavigationDestination(KeyClass::class)on the destination. The annotation is what drives KSP code generation. - The module that declares the destination doesn’t apply the
enro-processorKSP plugin. Every module that contains@NavigationDestinationannotations needs its own KSP dependency ondev.enro:enro-processor. - The app module doesn’t depend on the destination module. KSP-generated bindings only travel through direct dependencies — make sure your app module’s classpath includes every feature module that contains destinations.
If all three look correct, try a clean build (./gradlew clean :app:assembleDebug) — KSP incrementality can occasionally cache an old result. See the modular navigation recipe for the canonical multi-module setup.
Navigation-handle errors
${key} is a NavigationKey.WithResult and cannot be completed without a result
You called navigation.complete() (no arguments) on a destination whose key implements NavigationKey.WithResult<R>. A result-producing key must produce a result.
// ❌ won't compile if the key implements WithResult<LocalDate>
navigation.complete()
// âś… pass the result
navigation.complete(LocalDate.now())
If the destination genuinely has no result to deliver and you want it gone, use navigation.close() — the caller’s onClosed callback will fire.
Cannot completeFrom a NavigationKey.WithResult from a NavigationKey that does not also implement NavigationKey.WithResult
completeFrom(otherKey) says “delegate this destination’s completion to otherKey”. For that to make sense, both keys must agree on the result type: a WithResult<R> destination can only completeFrom another WithResult<R> with the matching R.
If the redirect target genuinely doesn’t have a result, you probably want closeAndReplaceWith(otherKey) instead.
Multiple onCloseRequested callbacks registered for the same NavigationHandle
You registered the close-requested callback in more than one place for the same destination — typically one in the ViewModel and one in the Composable, or two configure { } blocks in the same composable. Only one callback may be active at a time.
Pick one home for the callback (the ViewModel if you have one, otherwise the top-level Composable). See Navigation Handles → Overriding the close-requested callback.
Cannot execute NavigationOperation on TestNavigationHandle that is closed
The handle you’re using in a test was closed (or completed) and then you tried to drive more navigation through it. Either the test is exercising a flow that goes past the close, or the assertion is at the wrong point.
If you intentionally want to continue using the handle after a close, call handle.clearOperationHistory() between scenarios.
Result errors
Received result for id ${id}, but no active steps had that id
Specific to managed flows. The flow received a step result, but the flow scope no longer contains a step matching that id — usually because the flow’s body changed shape between the time the step was opened and the time the result came back (an upstream value caused a branch to no longer include this step, for example).
Make sure each open(key) inside the flow is reached deterministically from the upstream results. Don’t write conditions that produce a different ordering of open calls on re-evaluation; conditions are fine, but the same upstream results should always lead to the same flow shape.
Serialization errors
Object of type X could not be added to NavigationKey.Metadata
You’re storing a value in instance.metadata whose serializer isn’t registered. Built-in Kotlin types are fine; custom types either need to be @Serializable or have a serializer registered on the NavigationComponent:
@NavigationComponent
object MyComponent : NavigationComponentConfiguration(
module = createNavigationModule {
serializersModule(SerializersModule {
contextual(MyCustomType.serializer())
})
}
)
Backstack restoration fails after process death
Usually means one of your NavigationKeys has a non-serializable property. Mark the key as @Serializable and make sure every property on it is also serializable (either built-in, @Serializable itself, or registered in your SerializersModule). Run a simple “kill from Recents” test in development to flush these out early.
Testing errors
No NavigationHandle found ... in a test
You’re constructing a destination or ViewModel without setting up a test controller. Wrap the test in runEnroTest { }, or add an EnroTestRule to the test class.
@Test
fun example() = runEnroTest {
val handle = createTestNavigationHandle(MyKey)
// ...
}
For ViewModels with by navigationHandle<MyKey>(), also call putNavigationHandleForViewModel<MyVm, MyKey>(key) so the ViewModel can resolve its handle.
See Testing.
Multiple onCloseRequested callbacks ... in a test
Same root cause as in app code, but easier to hit accidentally if a fixture installs the callback and the test’s before block does too. Make sure each scenario only sets up the callback once.
Migration errors (Enro 2 → 3)
If you’re getting compile errors after upgrading from Enro 2, see the migration guide. The most common ones:
Unresolved reference: SupportsPush/SupportsPresent— the v2 markers are gone. Use bareNavigationKeyorNavigationKey.WithResult<R>.Unresolved reference: closeWithResult— renamed tocomplete(value).Unresolved reference: push/present— both replaced byopen. Dialog/overlay behaviour moves into destination metadata.Unresolved reference: NavigationApplication— gone. Install the controller directly fromApplication.onCreate.@Parcelizeflagged but not migrated — keys are now@Serializable(kotlinx.serialization).enroViewModelsno longer resolves — useviewModel { createEnroViewModel { ... } }.
Reporting an issue
If your problem isn’t covered here, the most helpful issue report includes:
- The full stack trace.
- A minimal code snippet showing the call site.
- The Enro version you’re on (
3.0.0-alphaXX). - The platform (Android API level, iOS version, JDK version, browser).