Keep Users Engaged with SFSafariViewController and Android Chrome Custom Tabs

Whether you’re displaying a simple “About” page or rendering content from an external source, pretty much every app ends up directing users to a web page at some point. I myself have used my fair share of WebView in my Xamarin work, but it’s never had quite the same posh as similar representations I’ve seen while using apps in the wild, such as Instagram, Facebook, or Reddit; in all of these experiences, clicking a link that would (should?) take you to the phone’s default web browser instead launched a nearly identical substitute directly within the app. Given how bland the shared WebView is, I was confident this adventure would require platform-specific implementations to achieve. But where to start?

A timely post from the Xamarin Developer Blog helped me identify the necessary control for iOS: SFSafariViewController. On the Android side of the house, tracking down the answer was a little more tricky – having not been in the Java developer game for quite some time, I had to did deeper, but eventually found the solution on an old Xamarin Developer blog post from 2015: Chrome Custom Tabs. With the necessary tools in hand, I could now set about implementing a solution. Let’s take a look:

tl;dr – Source Code

First, we need to define an interface in our shared application, which for this simple demo contains a single method:

    public interface INativeBrowserService
    {
        void LaunchNativeEmbeddedBrowser(string url);
    }

We’ll also need a way to trigger it. I’ve chosen a button, but theoretically nearly any event could be used to trigger the service.

    public partial class webviewsamplePage : ContentPage
    {
        private Abstractions.INativeBrowserService service;

        public webviewsamplePage()
        {
            InitializeComponent();

            // We rely on the built-in service lcoator in this example, but you could just
            // as easily locate this service using DI and launch from your ViewModel
            service = DependencyService.Get< Abstractions.INativeBrowserService >();
            {
                if (service == null) return;

                service.LaunchNativeEmbeddedBrowser("https://iwritecodesometimes.net/");
            };
        }
    }

Note well: in a real application, we (hopefully) have some manner of DI in place, so we wouldn’t need to use the Service Locator to resolve our platform-specific implementation(s), but it works in a pinch or if you’re just getting started.

Let’s turn our attention to the iOS project, where we implement the interface

using System;
using Foundation;
using SafariServices;
using UIKit;
using webviewsample.Abstractions;
using webviewsample.iOS.Services;
using Xamarin.Forms;

[assembly: Dependency(typeof(AppleNativeBrowserService))]
namespace webviewsample.iOS.Services
{
    public class AppleNativeBrowserService : INativeBrowserService
    {
        public void LaunchNativeEmbeddedBrowser(string url)
        {
            var destination = new NSUrl(url);
            var sfViewController = new SFSafariViewController(destination);

            var window = UIApplication.SharedApplication.KeyWindow;

            // TODO: Dangerous? Genuinely not sure if this will work after navigating
            var controller = window.RootViewController;

            controller.PresentViewController(sfViewController, true, null);
        }
    }
}

Keep an eye on the line where we identify the current controller: from this forum post, I’m not sure this will function once we start introducing navigation, but it will suffice for a simple example. We can then spin up a simulator to test our results:
Feb-26-2018 17-56-33
Wonderful! As you can see, we’re exposing some key functionality to the user, including the ability to share, manipulate navigation within the website and open in Safari proper.

Now, we shall do the same on Android. Since Chrome Custom Tabs are part of an Android Support Library, we’ll need to pay NuGet a visit first:

Install-Package Xamarin.Android.Support.CustomTabs -Version 26.1.0.1

Be sure to get the package that matches this exactly – there are a few deprecated libraries still lingering. If you have trouble installing, you may need to change the target version of the Android Project. This won’t (necessarily) affect your minimum target version, so don’t worry about losing some legacy users (just yet).

One Android Support is updated and happy with Custom Tabs component added, we can work on our implementation of the interface, like so:

using System;
using Android.App;
using Android.Content;
using Android.Support.CustomTabs;
using webviewsample.Abstractions;
using webviewsample.Droid.Services;
using Xamarin.Forms;

[assembly: Dependency(typeof(AndroidNativeBrowserService))]
namespace webviewsample.Droid.Services
{
    public class AndroidNativeBrowserService : INativeBrowserService
    {
        CustomTabsActivityManager customTabs;

        public void LaunchNativeEmbeddedBrowser(string url)
        {
            // TODO: We need the current actiivty. Forms.Context is deprecated, but I had issues
            // trying to use Android.App.Appllication.Context when casting to Activity, sooo...?
            var activity = Forms.Context as Activity;

            if (activity == null) return;

            var mgr = new CustomTabsActivityManager(activity);
            mgr.CustomTabsServiceConnected += delegate {
                mgr.LaunchUrl(url);
            };

            mgr.BindService();
        }
    }
}

Notice that I am relying on a deprecated API call to Xamarin.Forms – nobody is perfect! I couldn’t seem to get Android.App.Appllication.Context to cast to an Activity without throwing an exception, so I fell back on old habits. That embarrassment aside, we can now validate our Android implementation
Feb-26-2018 18-58-17

As you can see, we expose much of the same functionality as we’ve done on iOS.

And that’s it! If you have any tips or tricks for either of these controls (or would like to correct my questionable POC), sound off in the comments or Pull Request the source code.

2 thoughts on “Keep Users Engaged with SFSafariViewController and Android Chrome Custom Tabs

Leave a comment