Trifork Blog

Linking apps together with App Links

June 3rd, 2014 by
|

When developing apps for mobile phones you want to offer your users the best user experience. Sometimes this includes showing information outside of your app, in another app. Up until recently, there was no real good way to do this. Luckily, now there is a new initiative App Links that provides an open source and cross-platform solution for app-to-app linking. The initiative is supported by many mobile app developers, like Dropbox, Facebook, Spotify and Pinterest. In this post I will show you an example how to link between two Android applications using the open source implementation for Android Bolts. However, the same principles apply when you want to link between two iOS applications.

App Links

Creating an App Link can be done by simply adding a few metadata properties in the head tag of your HTML page. This metadata can be used to (deep-)link into your app or another app. If the user doesn’t have your app installed, the metadata can also be used to send the user to the correct app store to download the app. Another option that App Links provide is to take the user directly to the web view of the content.

One library that simplify the implementation of App Links is Bolts. Bolts provides helper methods to receive or navigate to an App Link.

In my example I have two apps, one with basic information about patients and one to register how long a treatment of this patient took (always one hour in my app). I have also two HTML pages which are used to get the metadata from that contains the information of which app needs to be opened.

Flow between App Links (Click for larger view)

HTML page 1 containing metadata to open the time tracker app:


</pre>
<pre><html>
  <head>
    <meta property="al:android:package" content="nl.trifork.patienttimetracker" />
    <meta property="al:android:app_name" content="Patient time tracker" />
  </head>
  <body>Patient time tracker</body>
</html>

Patients app

In the patients app I created the functionality to open the time tracker app. It will collect the metadata properties from the url of HTML page 1. The metadata contains the package name of the time tracker app, which will be used to create an Intent.
To check if the Intent could be opened by any of my installed apps I call the method PackageManager.resolveActivity(). If this method returns any ResolveInfo then it has found an app that could open your link. In my case it will of course find my time tracker app :). The time tracker will open the main activity as we didn’t add the metadata property for a classname.

If no specific target was found and you want the user to be send to the web browser, you’ll need to create a new Intent with the value of al:web:url or the orginal target url. You can choose for the option of not having a fallback. Then you should add the metadata property al:web:should_fallback is false to the head of your HTML page.

In order to send some data from the patients app to the time tracker app, we need to create a Bundle and add it to the Intent as an Extra named al_applink_data. Once we have done that we can start an Activity for our constructed Intent.

private void startActivityForAppLink(final String name, 
                                     final String gender, 
                                     final String dob, 
                                     final int time) {
  new WebViewAppLinkResolver(this)
      .getAppLinkFromUrlInBackground(Uri.parse(TIME_REGISTRATION_URL))
      .continueWith(
    new Continuation<AppLink, AppLinkNavigation.NavigationResult>() {
      @Override
      public AppLinkNavigation.NavigationResult then(
          Task<AppLink> task) {
        AppLink link = task.getResult();

        Intent intent = new Intent(Intent.ACTION_VIEW);
        AppLink.Target target = link.getTargets().get(0);
        intent.setPackage(target.getPackageName());
        intent.setData(link.getSourceUrl());

        ResolveInfo resolveInfo = getPackageManager()
            .resolveActivity(intent, 
            PackageManager.MATCH_DEFAULT_ONLY);
        if (resolveInfo != null) {
          Bundle extras = new Bundle();
          extras.putString("target_url", TIME_REGISTRATION_URL);
          extras.putString("name", name);
          extras.putString("gender", gender);
          extras.putString("dob", dob);
          extras.putInt("time", time);

          intent.putExtra("al_applink_data", extras);
          startActivity(intent);
       }
       return null;
     }
   }
 );
}

HTML page 2 containing metadata to open the Patients app to deep-link to the information of a patient:

<html>
  <head>
    <meta property="al:android:package" 
          content="nl.trifork.applinks" />
    <meta property="al:android:app_name" 
          content="Patient info" />
    <meta property="al:android:class" 
          content="nl.trifork.applinks.PatientItemActivity" />
  </head>
  <body></body>
</html>

Time tracker app

This app is created to receive incoming App Links. To make the app listen to Intents you need to specify an Intent filter to the activity that handles the incoming App Links:

<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <data android:scheme="http" />
</intent-filter>

To get the incoming App Link data you can call AppLinks.getAppLinkData(getIntent()). This will give you the information as a Bundle, which contains all the data we passed in from the Patients app.

Bundle appLinkData = AppLinks.getAppLinkData(intent);
if (appLinkData != null) {
  final String patientName = appLinkData.getString("name");
  TextView patientNameValueView = (TextView) 
      findViewById(R.id.patientNameValueView);
  patientNameValueView.setText(patientName);

  final String gender = appLinkData.getString("gender");
  final String dob = appLinkData.getString("dob");
  final int time = appLinkData.getInt("time");
}

Deep linking

When I have registered some time for a patient I want to update my view in the Patients app. The patient detail view is reachable from a list of patients and not directly in the app. I have configured an Intent filter on the detail view, so we are able to receive App Links.

I call the url of HTML page 2 which contains the property of the classname as metadata. With this metadata we can construct a more specific Intent the same way we did earlier. This way the package manager can resolve the corresponding view and we can deep link into our app with some extra data. On the flow image you can see that the registered time has been upped by one hour.

Other features of App Links

In my example I have used a website to get the metadata from, but it is also possible to publish your links via another service like Parse or Facebook. Also my apps are not available in the app store, but as said before App Links provides you with the option to send the user to the store when an app is not installed on your device.

You can download the full source code here: source code

3 Responses

  1. January 21, 2015 at 10:29 by Tom

    Thanks for the explanations, unfortunately, your sample project is not working for me.

    task.getResult(); returns null in PatientItemActivity even though the page1.html is accessible

  2. January 21, 2015 at 10:42 by Tom

    Okay, I had to post to realize I did’t put http:// in front of my url. My bad, it works fine. Thanks

  3. February 3, 2015 at 15:09 by laurence

    Great tutorial! However for some reason this is not working for me.. I get these messages in the console when trying.

    I/webclipboard﹕ clipservice: android.sec.clipboard.ClipboardExManager@41f4f3b8
    V/webkit﹕ BrowserFrame constructor: this=Handler (android.webkit.BrowserFrame) {42239138}
    I/GATE﹕ DEV_ACTION_COMPLETED
    D/WebView﹕ loadUrlImpl: called
    D/webcore﹕ CORE loadUrl: called
    D/webkit﹕ Firewall not null
    D/webkit﹕ euler: isUrlBlocked = false
    D/WebCore﹕ uiOverrideUrlLoading: shouldOverrideUrlLoading() returnsfalse
    I/GATE﹕ DEV_ACTION_ERROR
    V/webkit﹕ reportError errorCode(-10) desc(The protocol isn’t supported.)
    I/GATE﹕ DEV_ACTION_COMPLETED

    Any ideas? Have I missed out a permission or something like this?