Exploring Chrome Customs Tabs on Android

Alongside Android Marshmallow, we saw the announcement of the Custom Tabs library for Android. Let’s take a look at what this new library does and how we can use it to provide an improved User Experience when web content is displayed within our applications.

Google recently announced the release of a new support library, known as Custom Tabs. Previously, developers had the choice of either launching the system browser or using a WebView which can often be both unreliable and unpredictable. However, a Custom Tab allows you to open web URLs within the context of your application using the Chrome Browser installed on the device. These Custom Tabs help to provide a seamless experience when transitioning between your apps native content and the web. Aside from the improved User Experience from these Custom Tabs. The difference in performance, as shown below, is amazing!

Credit: Google

Chrome custom tabs load just short of two times faster than the standard browser intent and even more so than loading web content in a webview. The graph on the left demonstrates the reduced waiting times for users when using Custom Tabs.

Alongside this, there are some additional benefits:

  • UI customisation allows colouring of the toolbar, adding of action button and menu items, applying custom enter/exit animations and using a custom close icon
  • Performance optimisations can be made by pre-warming the Chrome Browser, allowing it to launch more quickly. You can also provide a URL that is likely to be launched, again allowing the pre-loading of content for a quicker launch
  • Users aren’t required to reconnect to sites that they are already connected to. This is because the cookie jar and permissions are shared through the Chrome browser
  • Data Saver, if enabled, will still function with the use of Custom Tabs – meaning users can still benefit from Data Saving mode
  • The browser is able to deliver a callback to our activity when external navigation takes place, allowing us to act accordingly

Convinced yet? You bet! You can get started implementing this experience right away, starting by adding the following dependancy to your project:

compile 'com.android.support:customtabs:23.0.0'

Note: Currently you require Chrome Dev installed on the Android device to launch custom tabs.


To see for yourself just how nice Chrome Custom Tabs are, it helps to have a little play for yourself on a real device.

For that reason I put together Tabby, a little sample app that you can find on github here.


Stronger Branding

As previously mentioned, one of the great things about Custom Tabs is the ability to customise the appearance of the window containing the web content. This new approach helps to maintain the current experience and provide a sense of familiarity for the user. It removes the feeling of leaving one app and transitioning into another, leaving you feeling as though you’re still within the same application. As a user, this makes me very happy.

Such a simple change can greatly improve the look-and-feel of an application

Chrome Tabs Support

When an ACTION_VIEW intent is launched, the users default browser is automatically opened. If the version of Chrome launched here supports Chrome Tabs then it will be exposing a Chrome Tabs service which we can bind to. When this is the case, the specified URL will be loaded in the launched Custom Chrome Tab as expected. However, if there is no Chrome Tab support available then we can provide a fall back and load the web content in a standard web view instead.

Getting started

  • We begin by initialising our CustomTabsActivityHelper, this helper is not part of the support library, but a custom class created to handle all tasks that communicate with our activity. We do this within the MainActivity during onCreate.
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
    mCustomTabActivityHelper = new CustomTabActivityHelper();
}
  • Next we need to bind our CustomTabsService, which we do during onStart – we then unbind this service whenever onStop is called. We do this so that we unbind the service when we leave the activity, say when our Custom Tab launches and our app is foregrounded, then bind again once we return.
@Override
protected void onStart() {
    super.onStart();
    mCustomTabActivityHelper.bindCustomTabsService(this);
}

@Override
protected void onStop() {
    super.onStop();
    mCustomTabActivityHelper.unbindCustomTabsService(this);
}
  • During the binding process, we begin by calling getPackageNameToUse() from our CustomTabsHelper class. This checks for packages available that support ACTION_CUSTOM_TABS_CONNECTION action.
  • If no packages are available, then our WebviewFallback is used to launch the URI in our custom WebViewActivity (consisting of a web view).
  • If the required package is available then we instantiate our CustomTabsServiceConnection, which is used to listen for the connection status of our service, then finish by binding our CustomTabsService.
String package = CustomTabsHelper.getPackageNameToUse(activity);

// If we cant find a package name, it means there's no browser that // supports Chrome Custom Tabs installed. So, we fallback to the 
// webview
if (package == null) {
    if (fallback != null) {
        fallback.openUri(activity, uri);
    }
} else {
    customTabsIntent.intent.setPackage(package);
    customTabsIntent.launchUrl(activity, uri);
}
  • Once we’re connected, we use the warmup() method of the CustomTabsClient class to begin warming up chrome ready for use – as previously mentioned, this prepares chrome ready for launch.
  • Next we use our ConnectionCallback interface to notify our activity that the service is connected. You can use this callback to make any necessary UI changes to your activity.
mClient = client;
mClient.warmup(0L);
if (mConnectionCallback != null) {
// let our activity know, we can make UI changes if need be
    mConnectionCallback.onCustomTabsConnected();
}
//Initialize a session as soon as possible.
getSession();
  • Finally, if a CustomTabSession doesn’t already exist then create a new one for use. Otherwise we return the existing session to be re-used.
if (mClient == null) {
    mCustomTabsSession = null;
} else if (mCustomTabsSession == null) {
    mCustomTabsSession = mClient.newSession(null);
}
return mCustomTabsSession;
  • If the service becomes disconnected at any point, then we use our ConnectionCallback interface to notify the MainActivity of this. Again, we can use this callback to make any UI changes if required.
mClient = null;
if (mConnectionCallback != null) {
// let our activity know, we can make UI changes if need be     
    mConnectionCallback.onCustomTabsDisconnected();
}

Now we’re all setup and ready to go, how can we make it look great?

Styling the Address Bar

Without setting the colour of our toolbar and enabling the title to be displayed, our CustomTab can look kind of ugly – which we don’t want!

No one likes an ugly CustomTab

To get our toolbar the attention it deserves, we can set the colour and enable the title using:

int color = getResources().getColor(R.color.ikea_blue);
intentBuilder.setToolbarColor(color);
intentBuilder.setShowTitle(true);

Pretty straightforward, right? Those 3 lines of code really make a difference, and you can see that our toolbar now looks beautiful.

Setting the colour and endbaled the title make our toolbar look great!

Note: There currently seems to be no control over the color of the text displayed in the toolbar. The system currently handles this by using either black or white text depending on the shade of the background colour used.

For example, this text would look great white – but the inability to set it manually lets it down. If the toolbar is a brand specific colour, then there isn’t much you can do with regard to the text.

This would look much better if it was white…

Action Buttons

It’s possible to add a single action button to the toolbar within our Custom Tab. We can set the following properties for this action button:

  • Label – The label used for the action button
  • Icon – The icon for the action button displayed in the toolbar
  • PendingIntent – The Intent launched when the action button is pressed

The implementation of this is again fairly simple. One thing to note is that the decoding of a bitmap from a drawable resource should be done asynchronously and not on the UI thread – for an example of this please see the sample app. The action button can be added as below:

String shareLabel = getString(R.string.label_share);
Bitmap icon = ImageUtils.decodeBitmap(R.drawable.icon_share);
PendingIntent pendingIntent = createPendingShareIntent();
intentBuilder.setActionButton(icon, shareLabel, pendingIntent);
private PendingIntent createPendingShareIntent() {
    Intent actionIntent = new Intent(Intent.ACTION_SEND);
    actionIntent.setType("text/plain");
    actionIntent.putExtra(Intent.EXTRA_TEXT, "Share text");
    return PendingIntent.getActivity(
            getApplicationContext(), 0, actionIntent, 0);
}

Menu Items

We can add multiple menu items to the options menu in the Custom Tab. The properties that we can set for each of these menu items are as follows:

  • Title – The title to be displayed for the menu item
  • Intent – The intent to be launched upon a menu item click event, this will behave like a standard android intent e.g Share or Email
The chimp is thrilled that we could add “Share” and “Email us” menu items to the options menu

The result shown in the above screenshot was achieved as below:

PendingIntent menuItemPendingIntent = createPendingShareIntent();
String shareItemTitle = getString(R.string.menu_share_title);
intentBuilder.addMenuItem(shareItemTitle, menuItemPendingIntent);
String mailItemTitle = getString(R.string.menu_email_title);
intentBuilder.addMenuItem(mailItemTitle, menuItemPendingIntent);
private PendingIntent createPendingShareIntent() {
    Intent actionIntent = new Intent(Intent.ACTION_SEND);
    actionIntent.setType("text/plain");
    actionIntent.putExtra(Intent.EXTRA_TEXT, "Share text");
    return PendingIntent.getActivity(
            getApplicationContext(), 0, actionIntent, 0);
}

Note: Whilst you can add a large number of menu items, it’s advisable to a minimum to ensure you don’t overwhelm the user (and not spoil the UI…)

Enter & Exit Animations

The CustomTab also allows us to define the animations that are used when the CustomTab transitions both on and off of the screen. Whilst this is great, you should keep this simple – so don’t go doing anything crazy.

Don’t let Uncle Ben down

By default, if we don’t set any animations then the Custom Tab will enter from the Bottom to the Top and exit from the Top to the Bottom.

Setting these animations is simple, using our intentBuilder instance we can individually assign our Start and Exit animations, as shown below:

intentBuilder.setStartAnimations(this, 
        R.anim.slide_in_right, 
        R.anim.slide_out_left);
intentBuilder.setExitAnimations(this, 
        android.R.anim.slide_in_left,    
        android.R.anim.slide_out_right);

Once setting the above on your CustomTabIntent instance, you’ll see the animation used in the screen on the right (which is nicer than the left) :

The Custom Tab now animates in from the right-to-left and exits left-to-right

Customising the Close Button

It is also possible to customise the close button shown on the left side of the toolbar, this again allows you to keep a consistent theme between your application and the Custom Tab.

Similar to adding an action button, we can set the close icon like so:

Bitmap icon = ImageUtils.decodeBitmap(R.drawable.icon_back);
intentBuilder.setCloseButtonIcon(bitmap);

Note: Here I am using a method from the ImageUtils class in my sample app to decode the bitmap off of the UI thread.

And that’s it!

I hope from this post you’ll see that adding Chrome Custom Tabs support to your application is a pretty straightforward process and provides many benefits. I’m really excited to be able to use this in upcoming projects and see the difference it makes to a finished product. Whilst it is not a complete replacement for a webview in cases where we may require specific customisations to the component itself, it makes for a massive improvement when you don’t require such behaviour. So a big thanks to the team at Google for giving us another great library to #buildBetterApps.

I’d love to hear your thoughts on this, or any approaches you’ve discovered that I may have missed out on. If so, please feel free to leave a response or drop me a tweet!

If you enjoyed this article then please hit the recommend button!

Leave a Reply

Your email address will not be published. Required fields are marked *