Using Chrome Custom Tabs with the Navigation Component from Android Jetpack

Chad Schultz
4 min readJun 16, 2020

--

If you need to show a web page in an Android app, you could use a WebView, open in an external browser, or use Chrome Custom Tabs. Chrome Custom Tabs will usually be your best choice.

For the overall structure of your app, you may be using an Activity for every task, with a Fragment for every screen, or reusable portion of a screen (this was the recommendation from Google for quite a while). Perhaps you don’t use Fragments, and have many Activities. Hopefully you don’t have all of your code packed into a single Activity!

Now there is a new way. The Navigation component in Android Jetpack is meant to be a substantial improvement. It’s a very different paradigm. Rather than directly launching Intents to Activities and Fragment, you define an XML “navigation graph”, and then tell Navigation what you want to do. It will launch the appropriate screen.

This is all well and good, but the Android Jetpack team didn’t seem to have Chrome Custom Tabs in mind. Here’s an example of how to open a Chrome Custom Tab from Google’s own documentation:

String url = ¨https://paul.kinlan.me/¨;
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(this, Uri.parse(url));

Now here’s how you go to another screen in the Navigation component, from the Navigation documentation:

val action =
SpecifyAmountFragmentDirections
.actionSpecifyAmountFragmentToConfirmationFragment()
view.findNavController().navigate(action)

See the problem? When we use Navigation, we need to make sure to go through the NavController. But Chrome Custom Tabs wants to launch itself, using a standard Intent. The two aren’t compatible.

What can we do about that?

Fortunately, I stumbled upon a relevant article, Add Chrome Custom Tabs to the Android Navigation Component. Kudos to Oliver Spryn for the very helpful information!

It seems we need to make a custom Navigator class that can tell the Navigation Controller how to launch a specific destination. Perfect. Instead of relying on the Navigation component to automatically open a Fragment, we can tell it to let Chrome Custom Tabs launch itself.

The article was very helpful, but I needed the integration to do a bit more, so I had to extend the solution a bit. Let’s walk through what I ended up doing. Most of the functionality is in a single class, the custom Navigator:

There’s a lot going on there, but the comments should be helpful. We’re doing a few things here:

  1. Collecting the values of optional attributes that may be specified in the nav graph
  2. Debouncing multiple rapid attempts to open the same URL in Chrome Custom Tabs
  3. Opening the URL in Chrome Custom Tabs

We need several other steps to make it all come together. First off, we the Navigation component to actually recognize our special Navigator as one of the ways it can navigate. How? We need to subclass NavHostFragment. Then we will make sure our app’s navigation uses that instead of the standard one. We’re just making sure to add our special Navigator to its internal list of Navigators, giving it another arrow in its quiver. Like so:

How do we use our EnhancedNavHostFragment? That’s easy. Your main Activity, which manages all your Navigation, just needs one slight tweak:

Our nav host Fragment is just changing its android:name to use our special class: android:name=”com.example.chromecustomtabsnavigator.EnhancedNavHostFragment”

And presto, now the Navigation component can use our new Navigator.

Speaking of our Navigator, it uses a lot of new XML attributes.Those XML attributes we reference need to be specified. You’ll need to create an attrs.xml file in your res\values folder (or add this to that file):

We’ll use those attributes in our nav_graph.xml file to add a Chrome Custom Tabs destination to the navigation graph. Instead of a <fragment>tag, we’ll use a <chrome> tag. This is just one possible way to set it up, for a destination I’m arbitrarily calling “GitHubRepoDetail” but that you could call “MyDestination” or whatever else you want. You can set whichever attributes you want from what we set up in attrs.xml.

Why are we using a tag called <chrome>? Flashback to the top of ChromeCustomTabsNavigator.kt:

@Navigator.Name("chrome")
class ChromeCustomTabsNavigator(

Ok. Now we have our custom Navigator, and the NavHost recognizes it. Its tag names and attributes are configured, and we have a destination set up in nav_graph.xml. We’re almost there!

Here’s an extension function for an optional shortcut I like to use:

This step is optional. If you want to skip it, you’ll just need to remember to use findNavController().navigatorProvider.getNavigator(ChromeCustomTabsNavigator::class.java) instead of findChromeCustomTabsNavigator() to get a reference to our custom Navigator class.

Now it’s show time! In the Fragment you want to open a Chrome Custom Tab, make sure to add this to onStart:

override fun onStart() {
super.onStart()
findChromeCustomTabsNavigator().bindCustomTabsService()
}

Now you can pepper your code with statements like:

findChromeCustomTabsNavigator().mayLaunchUrl(url)

(to possibly pre-load a likely URL) or

val action =
GitHubRepoListFragmentDirections.actionGitHubRepoListFragmentToGithubRepoDetail(
url
)
binding.root.findNavController().navigate(action)

(Your Directions and Actions will have different names).

Voila! You are now able to launch Chrome Custom Tabs, using the Navigation component, and you have a plethora of attributes to tweak the appearance of the Chrome Custom Tab.

--

--