Hi everyone, happy Monday. Is there a best practic...
# compose
c
Hi everyone, happy Monday. Is there a best practice on using Custom Tabs with Compose Navigation? I currently have something like the following:
Copy code
composable(...) {
  val context = LocalContext.current
  SideEffect {
    CustomTabsIntent.Builder().build().launchUrl(context, myUrl)
  }
}
I’ll be expanding on this a bit to warm up the browser, etc, but is this basically what I need to do? Call
launchUrl()
from inside the Composable?
i
I'm a bit confused about your code snippet. If you're wanting to take advantage of warming up the browser, then you need to do that well before you actually call
launchUrl
, so it wouldn't make any sense to put that code in the same place that is calling
launchUrl
And trying to repurpose a
composable
destination for something that isn't a screen built using a composable UI is not the right approach
c
Yeah it’s a bit half-baked at the moment. Would it make more sense to define my own
NavDestination
?
i
So you have two approaches: • don't try to integrate with Navigation Compose at all - just have a hoisted object above your NavHost that does the pre-warming and exposes a
launchUrl
method that any destination can use • Create your own custom
Navigator
for Custom tabs that has its own subclass of
NavDestination
and its own
NavGraphBuilder.customTab
extension method that adds that destination to your graph
You might take a look at the WIP bottom sheet integration for how that second approach might look like: https://github.com/androidx/androidx/pull/173
c
ok thanks for the tips Ian! I’ll try these out to see what works for me
i
I'd particularly look at the
BottomSheetNavDemo.kt
file there to see what it would look like as a consumer. I'd imagine something like:
Copy code
val navController = rememberNavController()
val customTabNavigator = rememberCustomTabNavigator()
SideEffect { navController.navigatorProvider += customTabNavigator }

NavHost(...) {
  composable(...) {
  }
  customTab("about", "<http://www.example.com>") // Don't even need a lambda for the simple case
}
You might also look at the existing
ActivityNavigator
and its
activity
destination builder for examples of how those bits might be implemented: • https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:navigati[…]untime/src/main/java/androidx/navigation/ActivityNavigator.kthttps://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:navigati[…]avigatorDestinationBuilder.kt?ss=androidx&amp;q=ActivityNavigator (they do a lot more than you need, given that CustomTabsIntent does so much for you)
c
Thanks a ton, I appreciate this 👍
Hi Ian, a follow-up question. I’m unsure about whether I should be using
SideEffect
when adding the Navigator to the NavController. I’ve explained here: https://github.com/androidx/androidx/pull/173/files#r668897941
i
Yes, that's exactly what you should be using
c
Using SideEffect leads to an IllegalStateException. Here’s a simple, complete example of what I mean:
Copy code
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()
            val customTabsNavigator = CustomTabsNavigator()
            SideEffect {
                navController.navigatorProvider += customTabsNavigator
            }
            NavHost(
                navController = navController,
                startDestination = "home",
            ) {
                customTab("home")
            }
        }
    }
}

@Navigator.Name("CustomTabsNavigator")
class CustomTabsNavigator : Navigator<CustomTabsNavigator.Destination>() {

    override fun createDestination(): Destination {
        return Destination(this)
    }

    class Destination(navigator: CustomTabsNavigator) : NavDestination(navigator)
}

fun NavGraphBuilder.customTab(
    route: String,
    arguments: List<NamedNavArgument> = emptyList(),
    deepLinks: List<NavDeepLink> = emptyList(),
) {
    addDestination(
        CustomTabsNavigator.Destination(provider[CustomTabsNavigator::class]).apply {
            this.route = route
            arguments.forEach { (argumentName, argument) ->
                addArgument(argumentName, argument)
            }
            deepLinks.forEach { deepLink ->
                addDeepLink(deepLink)
            }
        }
    )
}
Running this produces:
Copy code
java.lang.IllegalStateException: Could not find Navigator with name "CustomTabsNavigator". You must call NavController.addNavigator() for each navigation type.
If I remove SideEffect, it runs fine.
i
Do you mind filing an issue so we can dig into this further? It would be great to make sure our guidance, what APIs we provide, and what actually works are all actually in sync 😅 https://issuetracker.google.com/issues/new?component=409828
c
Thanks!
a
@Chris Fillmore Do you have a workaround for now?
i
There's already a workaround in the bug: you can apply the Navigator as part of composition