Thread
#compose
    Chris Fillmore

    Chris Fillmore

    1 year ago
    Hi everyone, happy Monday. Is there a best practice on using Custom Tabs with Compose Navigation? I currently have something like the following:
    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

    Ian Lake

    1 year ago
    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
    Chris Fillmore

    Chris Fillmore

    1 year ago
    Yeah it’s a bit half-baked at the moment. Would it make more sense to define my own
    NavDestination
    ?
    i

    Ian Lake

    1 year ago
    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
    Chris Fillmore

    Chris Fillmore

    1 year ago
    ok thanks for the tips Ian! I’ll try these out to see what works for me
    i

    Ian Lake

    1 year ago
    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:
    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.kt https://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)
    Chris Fillmore

    Chris Fillmore

    1 year ago
    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

    Ian Lake

    1 year ago
    Yes, that's exactly what you should be using
    Chris Fillmore

    Chris Fillmore

    1 year ago
    Using SideEffect leads to an IllegalStateException. Here’s a simple, complete example of what I mean:
    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:
    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

    Ian Lake

    1 year ago
    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
    Chris Fillmore

    Chris Fillmore

    1 year ago
    Thanks!
    a

    allan.conda

    1 year ago
    @Chris Fillmore Do you have a workaround for now?
    i

    Ian Lake

    1 year ago
    There's already a workaround in the bug: you can apply the Navigator as part of composition