Olivier Patry
12/06/2025, 12:49 PMNavKey subclass specific properties.
Say I have MyAppNavKey which extends NavKey and this interface defines a property to be implemented by final routes/NavKey.
The idea would be to define the transition spec at NavDisplay level, based on targetState or initialState, and MyAppNavKey.someProperty, I could take the decision of which transition to apply.
The issue I have is that I can't access NavEntry.key which is private, but this is the object I'd need to take the decision.
ContentKey is a string I can't rely on.
Ideally, I don't want to impact any entry<Foo> call sites of the entryProvider {}, everything should be statically declared in each route.
data object MyFinalRoute(override val someProperty: Boolean) : MyAppNavKeyOlivier Patry
12/06/2025, 12:50 PMtransitionSpec = {
val targetKey = targetState.entries.lastOrNull().key as? MyAppNavKey
val isTargetModal = targetKey?.shouldDisplayAsModal ?: false
if (isTargetModal) {
slideInVertically { fullHeight -> fullHeight } togetherWith ExitTransition.KeepUntilTransitionsFinished
} else {
slideInHorizontally { fullWidth -> fullWidth } togetherWith slideOutHorizontally { fullWidth -> -fullWidth }
}
},Seb
12/06/2025, 3:29 PMOlivier Patry
12/06/2025, 3:44 PMentry<R>) which painful.
Also, some routes are conditionally defined, the custom property of the route driving the transition might depend on some condition (thus, the property being defined at route level).
So, it's too static too.Ian Lake
12/06/2025, 3:57 PMentry ala myAppEntry that you enforce your developers to use which is what sets the default metadata. That way, you still define the logic in exactly one placeOlivier Patry
12/06/2025, 6:46 PMentry<R> implementation
1. I can't duplicate it and reuse original impl
2. I don't see how I can get the key object (only the key class) at this point 🤔Olivier Patry
12/06/2025, 6:50 PMentryProvider {} DSL, maybe I can do it without it?Olivier Patry
12/06/2025, 7:07 PMfun createMetadata(key: MyAppNavKey): Map<String, Boolean> = mapOf(
"modal" to key.shouldDisplayAsModal
)
fun <T : MyAppNavKey> NavEntry<T>?.isModal() = this?.metadata?.get("modal") == true
fun <T : MyAppNavKey> Scene<T>.isModal() = entries.lastOrNull().isModal()
NavDisplay(
...
transitionSpec = {
val isTargetModal = targetState.isModal()
if (isTargetModal) ... else ...
},
popTransitionSpec = {
val isInitialModal = initialState.isModal()
if (isInitialModal) ... else ...
},
...
entryProvider = { key ->
val metadata = createMetadata(key)
when (key) {
MyRoute -> NavEntry(key, metadata = metadata) {
...
}
...
}
}
)
}
I imagine, I could create my own DSL to simplify this and avoid the fact that each route must bind the proper metadata.
Is that what you suggested?
Do you think there is a way to keep the original DSL impl and achieve the metadata computation properly JUST with a custom myAppEntry function?Olivier Patry
12/06/2025, 8:22 PMclass EntryBuilderScope<K : NavKey> {
val map = mutableMapOf<KClass<*>, (K) -> NavEntry<K>>()
inline fun <reified T : K> entry(noinline block: @Composable (T) -> Unit) {
map[T::class] = { key ->
val isModal = (key as? LotoNavKey)?.shouldDisplayAsModal ?: false
val metadata = mapOf("modal" to isModal)
NavEntry(key, metadata = metadata, content = { block(key as T) })
}
}
fun build(): (K) -> NavEntry<K> = { key ->
map[key::class]?.invoke(key)
?: error("No entry registered for ${key::class}")
}
}
fun <K : NavKey> modalAwareEntryProvider(routes: EntryBuilderScope<K>.() -> Unit) = EntryBuilderScope<K>().apply(routes).build()
private fun <K : NavKey> NavEntry<K>?.isModal() = this?.metadata?.get("modal") == true
private fun <K : NavKey> Scene<K>.isModal() = entries.lastOrNull().isModal()
fun <K : NavKey> modalAwareTransition(): AnimatedContentTransitionScope<Scene<K>>.() -> ContentTransform = {
val isTargetModal = targetState.isModal()
if (isTargetModal) {
slideInVertically { it } togetherWith ExitTransition.KeepUntilTransitionsFinished
} else {
slideInHorizontally { it } togetherWith slideOutHorizontally { -it }
}
}
fun modalAwarePopTransition(): AnimatedContentTransitionScope<Scene<LotoNavKey>>.() -> ContentTransform = {
val isInitialModal = initialState.isModal()
if (isInitialModal) {
EnterTransition.None togetherWith slideOutVertically { it }
} else {
slideInHorizontally { -it } togetherWith slideOutHorizontally { it }
}
}
Here a working custom impl with DSL
Not 100% satisfied to not rely on the built-in DSL "just" because NavEntry<R>.key is private 😅Ian Lake
12/07/2025, 12:02 AMshouldDisplayAsModal to every key in your whole app) is what caused this additional problem (how can I get the shouldDisplayAsModal from my key out).
Maybe it would be better to take a step back and talk about what brought you to adding shouldDisplayAsModal in the first place, because generally that is not what should be in your key in the first place - the key should be the identity of your screen, not a place to stash unrelated flagsOlivier Patry
12/07/2025, 11:13 AMOlivier Patry
12/07/2025, 11:14 AM