One of the most painful things we deal with at Branch on a regular basis are the poor attempts of platform developers to implement support for 3rd party deep linking. First, let me set some context so you know who this post is targeted to. In simplified deep link execution, there is an origin and a destination. The origin is where the user interacts with the link, often a platform where users consume links such as email, Facebook, a paid ad, or many others. The destination is the target application that should be opened to deliver a consistent experience to the user.
If you’ve heard of Branch or read anything on this blog, so far, we’ve focused on best practices to implement deep linking in the destination. However, once and for all, we wanted to write the definitive post for the 1%, or those who are working to engineer the platforms that the 99% of the market depend on. To name some names, if you work at Facebook, Twitter, Reddit, Snap, Firefox, Google or any other app where you need to handle 3rd party links, this post is your guide on how to implement deep linking out of your platform successfully.
The most common issues we see on these platforms is inconsistent handling of mobile app deep links versus web links, specifically when there is an in-app browser. Despite what may be shared publicly, it is technically possible to deep link to the 3rd party native app when installed and fallback to the in-app browser when the destination app is not installed. Let’s look at some code.
iOS Handling
Below is a snippet of code for handling all forms of URLs that will safely fallback to the in-app browser when the 3rd party destination app is not installed.
// I'd suggest extending this method to add a callback block to handle success/failure in your UI - (void)loadURL:(NSURL *)destinationUrl fallbackBrowser:(WKWebView *)webview { // First check if the destination is a URI scheme // OR is an iTunes URL for loading an App Store page NSError *error = NULL; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(?!(http|https)).*:////.*|.*itunes/.apple/.com.*" options:0 error:&error]; NSUInteger numberOfMatches = [regex numberOfMatchesInString:[destinationUrl absoluteString] options:0 range:NSMakeRange(0, [[destinationUrl absoluteString] length])]; // If there's a regex match, this means that the URL was URL-like (had ://) but did not contain http/https // This is indicative of a URI scheme for a third party application if (numberOfMatches > 0) { [[UIApplication sharedApplication] openURL:destinationUrl options:@{} completionHandler:^(BOOL success) { if (!success) { // This means that the app was not installed and the URI failed to resolve. // You can choose to fail silently here or deliver feedback to the user } else { // This means that the third party app was successfully loaded and your app // has likely been backgrounded at this point. } }]; } // If there was no regex match, this means that we're dealing with a 3rd party web link. // Note that it's possible that this web link is a Universal Link so we'll need to test that // before we go ahead and load the webview. else { // First we attempt to trigger the Universal Link with openURL but flag to fail if Universal Links fail // This logic will let us fallback safely to our webview. [[UIApplication sharedApplication] openURL:destinationUrl options:@{UIApplicationOpenURLOptionUniversalLinksOnly:@1} completionHandler:^(BOOL success) { if (!success) { // The Universal Link has failed and we can proceed to load this URL in our in-app webview NSURLRequest *url = [[NSURLRequest alloc] initWithURL:destinationUrl]; [webview loadRequest:url]; } else { // This means that the third party app was successfully loaded via a Universal Link // and your app has likely been backgrounded at this point. } }]; } }
The code is well documented and pretty self explanatory, but it allows you to support both 3rd party URI schemes as well as Universal Links in a safe way, all while falling back to your in-app browser when the app is not installed. Additionally, you get feedback on whether the deep link succeeded / failed so that you can modify the UI or implement tracking based on the outcome.
Android Handling
Android is far simpler than iOS to handle, since there’s a lot more transparency in how intents can be resolved in the OS. Below is a snippet to handle the routing:
// List of common browser package names String[] browserPackageNames = {"com.android.chrome", "org.mozilla.firefox", "com.UCMobile.intl", "com.sec.android.app.sbrowser", "com.opera.browser", "com.opera.mini.native", "com.microsoft.emmx"}; ]; void loadURL(Context context, URI destinationUrl) { boolean isAppOpened = false; try { // First, check if the URI can resolve an intent Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(destinationUrl); ResolveInfo activity = intent.resolveActivity(context.getPackageManager()); // Then check if the destination app is not a browser, because you want to load those in your own in-app browser if (activity != null && !Arrays.asList(browserPackageNames).contains(activity.activityInfo.packageName)) { // If it is a 3rd party app, then open it up! context.startActivity(intent); isAppOpened = true; } } catch (Exception ignore) { } // If no app was opened via an intent, load the URL in your custom in-app browser. if (!isAppOpened) { CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); CustomTabsIntent customTabsIntent = builder.build(); customTabsIntent.launchUrl(context, destinationUrl); } }
The way this code works is simply checking whether the destination app package name is a commonly used browser or note. It should cover the majority of cases, letting you load web pages inside your in-app browser but properly handling URI schemes, Android App Links and Google Play pages.
Hopefully this is helpful to you! Feel free to send me an email at [email protected] if you have questions. Ready to see how our team can help you deep link more effectively? Our sales team would like to set up a call to discuss more.