ursus
04/14/2026, 10:43 AMNav3, when I handle onNewIntent deeplink, like this
class MainActivity {
private val pendingDeeplink = MutableStateFlow<Deeplink?>(null)
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val deeplink = resolveIntent(intent)
pendingDeeplink.value = deeplink
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val backStack = rememberNavBackStack2<SubscriberNavKey>(MainKey) <---
LaunchedEffect(pendingDeeplink) {
pendingDeeplink
.collect { deeplink ->
applyDeeplink(backstack, deeplink) <----
pendingDeeplink.value = null
}
}
NavDisplay bla bla
}
}
}
If the app was on MainKey, and then went to background -- basically the app is running already, i.e. its not killed -- and then I apply the deeplink which say pushes some screen -- I always see MainKey screen for a split second, no matter if the next screen has transition animation or not, it's always seen briefly
I'd assume it's due to the way I apply the deeplink, via LaunchedEffect which runs after composition.
Is there a way to not have the MainKey be seen and have the deeplink applied "sooner" somehow?Konstantin Tskhovrebov
04/14/2026, 10:47 AMursus
04/14/2026, 10:48 AMVilgot Fredenberg
04/14/2026, 10:48 AMapplyDeeplink in ::onNewIntent?Konstantin Tskhovrebov
04/14/2026, 10:49 AMursus
04/14/2026, 10:49 AMursus
04/14/2026, 10:50 AMKonstantin Tskhovrebov
04/14/2026, 10:51 AMKonstantin Tskhovrebov
04/14/2026, 10:52 AMLaunchedEffect(pendingDeeplink) has no sense. MutableStateFlow is not changedursus
04/14/2026, 10:52 AMcollectAsState worksursus
04/14/2026, 10:53 AMclass MainActivity {
private val backstack = StateListWhatever<NavKey>()
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val deeplink = resolveIntent(intent)
applyDeeplink(backstack, deeplink)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NavDisplay bla bla(backstack)
}
}
}ursus
04/14/2026, 10:53 AMVilgot Fredenberg
04/14/2026, 10:57 AMlateinit and defer assignment to composition. This would crash if composition never happened prior to onNewIntent, but I'm not sure that's possible through non-programmatic means.ursus
04/14/2026, 11:01 AMVilgot Fredenberg
04/14/2026, 11:03 AMrememberNavBackStack2.Vilgot Fredenberg
04/14/2026, 11:06 AMursus
04/14/2026, 11:06 AMursus
04/14/2026, 11:07 AMclass MainActivity {
private lateinit var backstack: NavBackStack<NavKey>()
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val deeplink = resolveIntent(intent)
applyDeeplink(backstack, deeplink)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
backstack = savedInstanceState.getBackStack(KEY)
} else {
backstack = NavBackStack<NavKey>(MainKey)
}
setContent {
NavDisplay bla bla(backstack)
}
}
}Vilgot Fredenberg
04/14/2026, 11:11 AMursus
04/14/2026, 11:12 AMrememberSerializable(
serializer = NavBackStackSerializer(elementSerializer = NavKeySerializer())
) {
NavBackStack(*elements)
}
I'd imagine its just to use kotlinx.serialization, into string, pass that into the android savedstate as a blob
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(KEY, Json.encodeblabla(backstack, NavBackStackSerializer())
}Vilgot Fredenberg
04/14/2026, 11:26 AM@Parcelable, IBinder, and SparseArray)Vilgot Fredenberg
04/14/2026, 11:27 AMrememberSaveable for my backstack with entries being annotated with Parcelable. YMMVursus
04/14/2026, 11:27 AMKSerializer?Vilgot Fredenberg
04/14/2026, 11:28 AMrememberSerializable supports (the aforementioned) platform-specific typesursus
04/14/2026, 12:04 PMrememberSaveable for my backstack with entries being annotated with Parcelable.
btw why? doesn't that pull in android dependency when defining navkeys? or do you not mind?
If figured they went the @Serializable route out of the box for this exact reasonVilgot Fredenberg
04/14/2026, 1:33 PMrememberNavBackStack uses reflection or requires repeating all entry types via SavedStateConfiguration. Parcelable has no such issueursus
04/14/2026, 1:34 PM@Serializable requirement on keys then for?ursus
04/14/2026, 1:34 PMrememberNavBackStac() , right?Vilgot Fredenberg
04/14/2026, 1:35 PMVilgot Fredenberg
04/14/2026, 1:36 PMrememberNavBackStac() , right?
I just use
val backStack = rememberSaveable { mutableStateListOf<Any>(Home) }ursus
04/14/2026, 1:37 PM@Serializable on keys as well rightVilgot Fredenberg
04/14/2026, 1:38 PM@Parcelize
data object Home : Parcelableursus
04/14/2026, 1:38 PMursus
04/14/2026, 1:52 PMIan Lake
04/14/2026, 3:07 PMonNewIntent at all when we already talked about how you can receive that signal inside composition where your back stack is already defined? You don't need any of this MutableStateFlow+collect stuff
https://kotlinlang.slack.com/archives/CJLTWPH7S/p1775346591629329?thread_ts=1775315820.880149&cid=CJLTWPH7Sursus
04/14/2026, 3:11 PMsetContent, i.e. where the backstack reference is in scope, so I can still use the rememberNavBackStack()
Am I right?Ian Lake
04/14/2026, 3:15 PMursus
04/14/2026, 3:15 PMursus
04/14/2026, 5:51 PMursus
04/15/2026, 11:22 AMOnNewIntentProvider registered in composition is not process restore safe (on deeplink). As it's registered later, and can/will miss the onNewIntent (race)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val activity = LocalActivity.current as ComponentActivity
DisposableEffect(activity) {
Log.d("Default", "registering onNewIntentProvider")
val listener = Consumer<Intent> { value ->
Log.d("Default", "callback onNewIntent intent=${value.data}")
}
activity.addOnNewIntentListener(listener)
onDispose {
activity.removeOnNewIntentListener(listener)
}
}
AppRoot(appStateHelper)
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.d("Default", "onNewIntent")
}
}
i.e. when I say go to pay something into custom tabs, which are a different process, so my app is then eligible to get killed, so I kill it, and then on redirect/deeplink back to the app's process is started again, however - the odd bit - obviously activity.onCreate is called but intent there is null - and it immediately calls onNewIntent with the proper not null intent.
And that is a race between activty.onNewIntent and the OnNewIntentProvider registration in composition, which is a race and most of the time it misses by a lot
13:25:23.699 D onNewIntent
13:25:24.158 D registering onNewIntentProvider
half a second
-- unless I'm doing it wrongVilgot Fredenberg
04/15/2026, 11:32 AMursus
04/15/2026, 11:36 AMonCreate carry the intent and onNewIntent not get called here, but yea a race nonetheless