Mark
02/13/2024, 1:34 PMnavController
too soon after cold launch. For example, after force stopping the app and then sharing a file (from e.g. Solid Explorer) to my app, I get:
java.lang.IllegalArgumentException: Cannot navigate to NavDeepLinkRequest{ uri=<android-app://androidx.navigation/foo?uri=><encoded uri> }. Navigation graph has not been set for NavController androidx.navigation.NavHostController@66e3bed.
at androidx.navigation.NavController.navigate(NavController.kt:1803)
If I stick in an arbitrary length delay then it all works fine. But how to handle this properly?Mark
02/13/2024, 1:41 PMLaunchedEffect
and DisposableEffect
in the same composable that calls the NavHost
composable, then it seems to work. But I’d rather not do that (because violates single responsibility principle). So what’s the alternative? Some callback, state flag?Ian Lake
02/13/2024, 3:15 PMmimeType
when you use navDeepLink
to associate a particular destination with the handling of that type of IntentMark
02/14/2024, 2:27 AMNavDeepLink
need to have a uri that is typically using my app’s domain? There must be something somewhere that will map a generic SEND intent to a specific deep link uri.Ian Lake
02/14/2024, 3:52 AMMark
02/14/2024, 4:23 AMcomposable(
route = "foo",
deepLinks = listOf(
navDeepLink {
action = Intent.ACTION_SEND
mimeType = MIME_TYPE_ZIP
},
navDeepLink {
action = Intent.ACTION_VIEW
mimeType = MIME_TYPE_ZIP
},
),
) { backStackEntry ->
val deepLinkIntent: Intent? = backStackEntry.arguments?.getParcelable(NavController.KEY_DEEP_LINK_INTENT)
val uri = deepLinkIntent?.clipData?.getItemAt(0)?.uri
...
FooScreen(uri)
}
However, in my case the user can share various zip files to the app, but not all zip files are the same. Depending on the filename (or perhaps zip content), I might want to jump to different parts of the nav graph. So what’s the approach here? To have a kind of routing composable?Ian Lake
02/14/2024, 4:31 AMMark
02/14/2024, 4:34 AMMark
02/14/2024, 5:56 AMnavDeepLink
whereas it is possible in an intent-filter
. Is that intentional?Ian Lake
02/14/2024, 6:00 AMnavDeepLink
instances. You can, of course, provide your own wrapper around navDeepLink
, etc. if you find yourself repeating yourself multiple times - it is just Kotlin code 🙂Mark
02/14/2024, 6:13 AMIan Lake
02/14/2024, 6:14 AMIan Lake
02/14/2024, 6:36 AMviewDeepLink
helper method if you find yourself repeating the same action many times for instance - then it is one line per mime type and a lot more clear on how each of the fields interact (each deep link is evaluated separately), something the intent filter APIs do extremely poorlyMark
02/14/2024, 6:45 AMMark
02/15/2024, 9:49 AMnavController.handleDeepLink()
in onNewIntent()
though I guess that’s for the legacy navigation library. If also relevant for compose navigation, how best to obtain a reference to navController from there?Ian Lake
02/15/2024, 2:58 PMIan Lake
02/15/2024, 3:00 PMOnNewIntentProvider
APIs that ComponentActivity
implements: https://developer.android.com/reference/androidx/core/app/OnNewIntentProviderMark
02/16/2024, 2:58 AMIan Lake
02/16/2024, 2:59 AMMark
02/16/2024, 3:41 AMACTION_VIEW
with zip mime type working (the composable is not triggered). Using ACTION_SEND
works fine however. This ACTION_VIEW
is sent for example when using Open With from Solid Explorer.Mark
02/16/2024, 4:55 AMIan Lake
02/16/2024, 4:59 AMSavedStateHandle
(since all arguments are automatically propagated into that), you could just remove
the key after you're done processing itMark
02/16/2024, 6:37 AMIan Lake
02/16/2024, 6:50 AMNavController.KEY_DEEP_LINK_INTENT
at the individual destination levelMark
02/16/2024, 2:37 PMIan Lake
02/16/2024, 3:06 PMMark
02/19/2024, 12:39 PMIan Lake
02/19/2024, 4:48 PMIan Lake
02/19/2024, 4:49 PMMark
02/20/2024, 3:19 AMmovableContentOf
because when I remove that (and use same content for both orientations) the problem goes away.
val navHost1 = remember { movableContentOf(navHost1) }
val navHost2 = remember { movableContentOf(navHost2) }
if (isLandscape()) {
LandscapeContent(
appBar = appBar,
navHost1 = navHost1,
navHost2 = navHost2,
modifier = modifier,
)
} else {
PortraitContent(
appBar = appBar,
navHost1 = navHost1,
navHost2 = navHost2,
modifier = modifier,
)
}
Mark
02/20/2024, 7:56 AMBackHandler
inside the movableContentOf
(in other words straight after the NavHost
like you mentioned). And I guess a sensible way to enforce that is by doing something like this.
@Composable
fun NavHost(
navController: NavHostController,
drawerState: DrawerState,
startDestination: String,
modifier: Modifier = Modifier,
builder: NavGraphBuilder.() -> Unit,
) {
NavHost(
navController = navController,
startDestination = startDestination,
modifier = modifier,
builder = builder,
)
if (drawerState.isOpen) {
val scope = rememberCoroutineScope()
BackHandler {
scope.launch {
drawerState.close()
}
}
}
}