Launch an Android app from the web and cleanly fallback to web

I was writing about Service Discovery the other day and I have some thoughts about how we can do inter-app communication on the web more effectively than what we can today.

Interactions between web and web, web and apps and apps and web is something that many of you may know that I am passionate about, but it is incredibly hard and using the intent syntax in Android is a great start but has huge problems because it is not portable.

Intents on Android allow the user to use apps and services of their choice to complete actions offered by the App they are currently using and all the developer has to do is indicate the intent of the user to the system when they start the task. Intents such as SEND (read: Share), CALL, PICK and EDIT are all common actions that can be fulfilled by other apps on the users system.

The web on Android also has the ability to participate in this system too through the use of intent: syntax URLs.

intent:
   HOST/URI-path // Optional host 
   #Intent; 
      package=[string]; 
      action=[string]; 
      category=[string]; 
      component=[string]; 
      scheme=[string]; 
   end; 

The format of these URLs is pretty simple and an example of a real URL that you can use is:

intent://#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT=http://test.com;S.android.intent.extra.SUBJECT=Test;end

This will let us launch an app from the web that supports sharing plain text (think Twitter, Facebook link sharing) and pass data into the app that the user chooses. Whilst cool, this URL has a number of problems:

  1. It is not portable. Try this on a desktop or iOS.
  2. If the user is on Android and doesn't have an app installed that can handle the intent it won't work.

Let's deal with point 2 first. In Chrome 40 S.browser_fallback_url was introduced to all an intent to resolve to a web page if there is no app available to handle it. This is great for Android, it means that we can provide a web based fallback when there is no app available. But what about the massive issue of zero cross platform support?

Intent URL syntax has very clear benefits for Android users but unless every user has an app that can handle the intent: URL scheme or every platform adds the ability to handle the intent: URL syntax then it is not really a viable system to use for linking the web with the user's preferred apps.

I have been playing around with what we can do to bring a little bit of sanity to this situation. Ideally we want a solution that:

Before I get too deep, I will say that the solution I am going to talk about is only for the action of Sharing. Sharing is a concrete use-case (many sites have many widgets for sharing on) and has a wide range of services that can be integrated with.

For this Sharing hack, I have made the following assumptions:

  1. The user if all else fails will share to one endpoint such as Twitter or some other service.
  2. If the platform supports Intents, users will prefer to use their system
  3. Only Android supports intents :\
  4. No user state of preferred services will be hosted or stored

The first solution I created was a JS plugin along the lines of the following code.

encode all service integrations with intent: syntax and S.browser_fallback_urls

onload => if not Android convert all intent: to normal URLs by parsing out S.browser_fallback_url

It worked, but it was very crappy. It required JS, could block the main UI thread and potentially needed lots of DOM manipulation. I had a little think and remembered that if the user directly invokes an action that triggers a navigations to an intent: URL then it will be resolved, this includes a navigation to a URL that subsequently 302 redirects to an intent: URL.

This is promising. If I can do a redirect to intent: urls then I can encode sharing logic in a standard URL that redirects to a Sharing service (Twitter) and for Android users will still support the abilities of the intent: URL.

I set up a new Server on my host (I use nginx) that if the User Agent is Android then trigger.

server {
    listen 443 ssl http2;
    server_name share.kinlan.me;
  
    location / {
        if ($http_user_agent ~ Android ) {
          return 302 'intent://#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT=$arg_url;S.android.intent.extra.SUBJECT=$arg_text;S.browser_fallback_url=https://twitter.com/intent/tweet?text=$arg_text&url=$arg_url;end';
        }
        
        return 302 https://twitter.com/intent/tweet?text=$arg_text&url=$arg_url;
    }
    
    # ... rest of my config and SSL configy
}     

Check it out: https://share.kinlan.me/?text=woot+woot&url=https://paul.kinlan.me

There is a lot that can still be improved with this approach — such as the ability for the user to choose a preferred service with no central registry service — but there is also a lot to like too:

Have any suggestions, critiques or ideas then leave me a note.

I lead the Chrome Developer Relations team at Google.

We want people to have the best experience possible on the web without having to install a native app or produce content in a walled garden.

Our team tries to make it easier for developers to build on the web by supporting every Chrome release, creating great content to support developers on web.dev, contributing to MDN, helping to improve browser compatibility, and some of the best developer tools like Lighthouse, Workbox, Squoosh to name just a few.

I love to learn about what you are building, and how I can help with Chrome or Web development in general, so if you want to chat with me directly, please feel free to book a consultation.

I'm trialing a newsletter, you can subscribe below (thank you!)