Is there any compose sample that illustrates how d...
# compose
m
Is there any compose sample that illustrates how deep linking should be done in nested navigation with bottom bar ? Read More ⬇️
So I have a usecase where in I have a single activity architecture where I have a fragment and this fragment is responsible for holding all the navhost for composables
The structure of nav graphs is that I have 4 bottom bar icons so each bar has its own backstack. its like navHost() { graphA() graphB() graphC() graphD() } there is a common screen that can be accessed from any of the graphs. and this is screen which needs to deeplinking
How do I send/observe deeplink url from activity(through fragment) to the nested graphs so that they open up the common screen
s
1. Is this androidx.navigation? 2. If yes, is this navigation.compose? 3. Are you using androidx.navigation support for deep links?
m
Copy code
androidx.navigation:navigation-fragment-ktx (for fragment navigation)
androidx.navigation:navigation-ui-ktx 
androidx.navigation:navigation-runtime-ktx
androidx.navigation:navigation-compose
I've been using these
as for deepLink theres a hack that we;re relying on in our project which doesnt always work... so we need to do something else or some elegant solution if already provided by google
s
Does your graph setup look like this?
Copy code
NavHost() {
  navigation("graphA", startRoute = "...") {
    composable() {...}
  }
  navigation("graphB", startRoute = "...") {
    composable() {...}
  }
  navigation("graphC", startRoute = "...") {
    composable() {...}
  }
  navigation("graphD", startRoute = "...") {
    composable() {...}
  }
}
m
Exactly yes!!
s
Perfect so you are using navigation.compose
Do you have your deep links like this?
Copy code
composable(
  route = "...",
  deepLinks = listOf(
    navDeepLink { uriPattern = "https://..../...." },
  ),
) {...}
m
No... we havent used the actual deeplink support As I said earlier that we're relying on a hack.
would this work out of the box if we do this?
s
Yeap, I understand that. We use this for our app, and yes, it works “out of the box”, if you do it right 😄 So maybe you can start by looking a bit into the documentation of it which is here https://developer.android.com/guide/navigation/design/deep-link Unfortunately it’s not focused on the compose implementation now, but a lot of what it says like how the placeholders should be are still true
Here’s for example all of the deep links we have in our app here And then for example where it is used here Then if you notice the “…./terminate-contract?contractId={contractId}” pattern, and note the
contractId
placeholder here. Then how it is turned in to this class here The type-safety comes from using navigation-compose-typed. But the official implementation is coming with type safe impl soon too.
m
Thanks a lot mate... will look into this right away and share implementation results here Regarding type safety..i dont think we have to deal with that right now in our project.. we just pass 2 strings to that composable Btw these examples 🌟
s
Oh yeah skip it for now. But be careful for if you have deep links with placeholders that you define all the right names and types and nullability and so on properly. It can be a bit tricky sometimes. I dumped a shit ton of links on you all at once, so if something is confusing lmk, I would be happy to help
❤️ 1
https://developer.android.com/develop/ui/compose/navigation#deeplinks Oh found this too! This must be an important bridge of the docs I shared before to how it is done in compose.
m
Yeah all of this helped to implement this. Thanks @Stylianos Gakis
But I dont know the approach of how I am observing the deepklink
s
What do you mean by observing the deep link here?
m
The thing I am now concerned about is that I have an activity viewModel(MainViewModel) that is shared with fragment(that contains all of the composables). So I need to inform the navController whenever I recieved a deeplink. Inside the MainViewModel I have kept a stateflow of intent.. this MainViewModel is shared with fragment using activityViewModels. And I am collecting this deeplink(state) at the root of composables.. and do something like
Copy code
val intent by mainActivityViewModel.intent.collectAsState()
LaunchedEffect(key1 = intent) {
    Log.d("IntentReceived1", "$intent")
    Log.d("IntentReceived2", "${intent?.data}")
    navController.handleDeepLink(intent)
}
Copy code
viewModel.setIntent(intent)
I am setting this intent from activity's onCreate and from onNewIntent
Copy code
fun setIntent(intent: Intent?) {
    viewModelScope.launch {
        _intent.emit(intent)
    }
}
like this is viewModel
s
Is that fragment necessary? Could you just get rid of it completely to only have compose directly? I doubt what you really want here is to set the intent on the ViewModel
If you can do that, NavHost() will automatically pick up the intent which was sent to the Activity and it will automatically just handle the deep link for you
rememberNavController
will actually do the forwarding of the intent to the NavController, to be more precise
As noted here https://developer.android.com/guide/navigation/design/deep-link#handle, if you also just use the default launchMode you get the right behavior for free, and you do not need to write the
onNewIntent
override either
m
https://kotlinlang.slack.com/archives/CJLTWPH7S/p1712854009649169?thread_ts=1712684790.754969&cid=CJLTWPH7S Actually migration cost if high if we remove this fragment...hence need relying on this
s
Okay, so you want to keep the fragment. Can you look into changing the launchMode and then forward the intent to the NavController from the
onNewIntent()
?
m
Actually activity doesn't has the navController..its created at the fragment and from thereon every composable use it for navigation. our setup is quite unnecesarliy complicated(which we plan to address). something like Actvity -->RootNavigation(xml) -> AuthNavigation (xml) -> HomeNavigationNavigation (this contains the fragment that holds all composables and this is whre i create navController by rememberNavController.) Is there any way that I could create navController at the activity level and use it inside the fragment composable? that would I believe reduce the stateflow handling
s
Can you instead grab the activities intent from inside your fragment? In fact, LocalActivity should still be the same activity come to think of it. If you just do nothing does the NavController perhaps already pick this up? Which launchMode do you use in the app?
m
We just have a single activity..and it uses singleInstance Just to avoid activity recreation wheen navigating using deeplink
s
Then those docs mention that you must override onNewIntent. Then have your fragment also expose a
onNewIntent()
function and call it from your activity. Then in your fragment pass the intent into the NavController. With all that said, you may not actually want to change the launchMode, I don't know what your app is for but most of the time that is just the correct thing to do
m
We've a music playing app.. And a single activity architecture So in case a user is listening to something and goes out of the app and then comes back to it using deeplink then it may restart the app and launch new activity and then player may get destroyed in the process. I'll confirm this behaviour once tomorrow.
s
Hmm okay we are definitely not a music app, so I am gonna try to not make assumptions that are not true. For playing the music itself, isn’t that running on something not attached to the Activity itself like a foreground service? Which the app binds to instead? Which could mean that even if you have two instances of the app running (one in your own task and one from the deep link for example) they still bind to the same foreground service, and therefore allowing you to still use the default launchMode? Now if that sounds like a ton of bullshit forget about it. I am asking also because I am curious, as someone who’s never worked with this kind of stuff before. And with all that said, still give this a try https://kotlinlang.slack.com/archives/CJLTWPH7S/p1712861564857939?thread_ts=1712684790.754969&cid=CJLTWPH7S so forwarding the onNewIntent to the fragment which in turn forwards it to the NavController.
m
Yeah we're using media3 service... And yeah actually your explanation makes total sense. Will try this and get back with results.
s
Never used media3 myself, totally clueless when it comes to those things unfortunately 😅
m
So I gave it a go.. Actually with any launch mode, my app gets recreated when navigating using deeplinks. so service also restarts
I guess i'll have to keep it this way only by using viewModel to get around fragment and observe this intent in my composable
Thanks a lot for your solution for deeplink
s
my app gets recreated when navigating using deeplinks
What do you mean by this? Through normal behavior if you are deep linked into your app from inside another app you may have 2 instances of your app running at the same time, is that what you were experiencing here? Purely as a coincidence, I was showing an example of this here https://kotlinlang.slack.com/archives/C051P2HUVKP/p1712603841054079?thread_ts=1711657801.535509&cid=C051P2HUVKP at the last ~5 seconds of this video
m
Yeah its creating 2 instance of it.. and to avoid that we use different launch scope apart from standard. Reason to avoid 2 instance is that we have some static/global data using companion object(i know this approach sucks)
s
Well that makes sense then, maybe you can revisit this when you fix this problematic setup. So now don’t use the default launch mode and then follow the docs https://kotlinlang.slack.com/archives/CJLTWPH7S/p1712854170859039?thread_ts=1712684790.754969&cid=CJLTWPH7S regarding having to handle the newIntent yourself and see how that goes for ya 😄 I still think there’s room for you to not have to pass it to the VM and all that kind of stuff, with what I said here https://kotlinlang.slack.com/archives/CJLTWPH7S/p1712861564857939?thread_ts=1712684790.754969&cid=CJLTWPH7S
🙌 1