How to gracefully return to the site when Deep Link cannot be processed by the application

Situation:

  • You have an extensive mobile site, m.somewhere.com
  • On Google Play, you have an Android app that duplicates the key features of m.somewhere.com, but not all of them.
  • Your client / employer / investor has asked you to implement a deep link to the URLs that can be processed by the application.

TL; DR - How do you implement this?

My approach so far:

First instinct: match only specific URLs and run them. Problem: Limited expression in the inherited AndroidManifest filter prevents this (e.g. http://weiyang.wordpress.ncsu.edu/2013/04/11/a-limitation-in-intent-filter-of-android-application/ ).

As a subset of the problem, suppose that the server at m.somewhere.com knows that any URL that ends with a number goes to a specific page on the site, and marketing guys constantly work with seo, so, for example,

I want to run the application for:

http://m.somewhere.com/find/abc-12345 https://m.somewhere.com/shop/xyz-45678928492 

But not for

 http://m.somewhere.com/find/abc-12345-xyz https://m.somewhere.com/about-us 

no combination of path, pathPrefix or pathPattern can handle this.

Best practice for stackoverflow ( Corresponds to a URI with <data>, e.g. http://example.com/something in AndroidManifest ) seems to catch everything and then handle the situation when you get to onCreate (), and make sure you should not handle this particular URL:

Android Manifest:

 ... <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="m.somewhere.com" android:pathPattern=".*"/> </intent-filter> ... 

OnCreate () action:

 Intent i = getIntent() String action = i.getAction(); Uri uri = i.getData(); if (Intent.ACTION_VIEW.equals(action) && cantHandleUrl(uri)) { // TODO - fallback to browser. } 

I programmed something similar to what works, but this leads to a very bad user experience:

  • While browsing m.somewhere.com, each url has a hiccup while the application launches and then returns.
  • There is an unpleasant habit of a Chooser popup window for each link clicking on m.somewhere.com asking the user they would like to use (and the Android application is indicated along with the browsers, but clicking on the Android application will launch the selection screen again). If I'm not careful, I get an infinite resume loop for my application (if the user selects "Always"), and even if I'm careful, the user seems that their choice of "Always" is ignored.

What can be done?

(EDIT: Displaying a site in WebView in an application for raw pages is NOT an option.)

+7
android deep-linking
source share
6 answers

There is a somewhat hacky way to do this:

  • In the manifest, create an intent filter for m.somewhere.com to open the specific deeplink handler activity.
  • In this exercise, find out if your application supports this URL or not.
  • If so, just open any activity.
  • If this is not the case, send the unoccupied intent ACTION_VIEW , which will be opened by your browser. The problem here is that your application will also catch this intention, and this will create an endless loop if your application is selected as the default handler for this URL. The solution is to use PackageManager.setComponentEnabledSetting() to disable deeplink Activity handler activity before sending this intent and re-enable it after.

Code example:

 public class DeepLinkHandlerActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { Uri uri = intent.getData(); Intent intent = makeInternallySupportedIntent(uri); if (intent == null) { final PackageManager pm = getPackageManager(); final ComponentName component = new ComponentName(context, DeepLinkHandlerActivity.class); pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); Intent webIntent = new Intent(Intent.ACTION_VIEW); webIntent.setData(uri); context.startActivity(webIntent); AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void[] params) { SystemClock.sleep(2000); pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); return null; } }; task.execute(); } else { startActivity(intent); } finish(); } } 

Hope this helps.

Note. It looks like you need to delay re-enable for a couple of seconds for this to work.

Note 2: For a better experience, using the Transparent theme for your activity will look like your application didn’t even open.

Note 3: If for some reason your application crashes or is destroyed before the component is re-registered, you will lose support for deep links forever (or until the next update / reinstall), so I will also re-enable the component in App.onCreate() just in case.

+3
source share

URX provides a free tool (urxlinks.js) that automatically redirects users of mobile websites to the application if the application is installed. Documentation is available here: http://developers.urx.com/deeplinks/urx-links.html#using-urx-links-js

+1
source share

If two applications use the same scheme, then the selection screen will be displayed, since the android does not know which application the link is for. Using a custom schema for your application may solve this problem. But still you cannot be sure that no one else will use this scheme.

0
source share

It sounds like you're trying to view your mobile app and mobile website as extensions of the same experience. This is a good practice, generally speaking, but at the moment they are simply not at parity. At least until they reach parity, I would not recommend automatically pushing the end user into your mobile application, because users who intentionally use the mobile site to find content that is not in your application will find it incredibly frustrating.

Instead, it might make sense to use a smart banner to encourage users on mobile website pages that have an equivalent in the app to open the app. These banners would be your denouement. You can create them yourself or integrate a tool, such as Branch ( https://branch.io/universal-app-banner/ ), which handles deep links and smart banners.

This last part of your question is about where to place deep links. One of the advantages of using smart banners instead of redirecting is that you can embed them in the appropriate templates on your CMS, instead of relying on URL detection.

Good luck

0
source share

This was my solution to the second problem. PackageManager.queryIntentActivities () will provide you with a list of applications / actions that will be displayed in the selection. Go through the list (which should at least include the browser) and find the action whose package name does not match the current application and set the intent class name for it, then start Activity with this intent and end the call ();

 public Intent getNotMeIntent(Uri uri) { Intent intent = new Intent(Intent.ACTION_VIEW, uri); PackageManager manager = context.getPackageManager(); List<ResolveInfo> infos = manager.queryIntentActivities(intent, 0); for (int i = 0; i < infos.size(); i++) { ResolveInfo info = infos.get(i); // Find a handler for this url that isn't us if (!info.activityInfo.packageName.equals(context.getPackageName())) { intent.setComponent(null); intent.setClassName(info.activityInfo.packageName, info.activityInfo.name); return intent; } } // They have no browser return null; } 

A transparent theme (mentioned above) should be a good solution to the first problem.

0
source share

Late answer, but for future readers: if you support a minimum API level of 15 , then there is a more direct (less hacky) way to return to the browser for URLs that you understand do not want to process without resorting to disabling / re-enabling components for URL search.

nbarraille's answer is creative and perhaps the only option if you need to support APIs below 15, but if you don't, you can use Intent.makeMainSelectorActivity() to launch the user's default browser, which allows you to bypass the Android app selection dialog ResolverActivity .

Do not do this

So instead of re-broadcasting the Intent URL, the typical way is:

 // The URL your Activity intercepted String data = "example.com/someurl" Intent webIntent = new Intent(Intent.ACTION_VIEW, data); webIntent.addCategory(Intent.CATEGORY_BROWSABLE); startActivity(webIntent); 

Do it

Instead, you broadcast this intention:

 Intent defaultBrowser = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER); defaultBrowser.setData(data); startActivity(defaultBrowser); 

This will allow Android to load the browser application and data URL. This should bypass the selection dialog, even if they have more than one browser application installed. And without the dialogue of choice, you don’t need to worry about the fact that the application falls into an endless cycle of interception / re-broadcasting of the same intention.

Caveat

You should be fine by opening the URL (the one you don't want to handle) in a custom browser. If you want other applications other than a browser to be able to open a link, this solution will not work, because there is no selection dialog.

Trap

As far as I can tell, the only quirk from using this solution is that when a user clicks one of your deep links, they can choose to open in their application or in their browser, etc. When they select your application and your internal application logic implements a URL that it does not want to intercept, the user is immediately shown a browser. Therefore, they select your application, but a browser is displayed instead.

NOTE. When I say "broadcast" in this answer, I mean the general term, not the actual Android system.

0
source share

All Articles