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()
}
}
}
}