Question

We have an app that handles a custom URL scheme (vstream://). When someone comes to a web page that has some vstream:// content, we need to redirect them to the store if they don't have our app installed.

In iOS, we do this:

setTimeout(function() {
  window.location =
    "itms://itunes.apple.com/us/app/kaon-v-stream/id378890806?mt=8&uo=4";
}, 25);

window.location = "vstream:view?code=...stuff...";

If the window.location assignment fails, the timeout jumps over the App Store before the dialog box comes up. (I found this technique here: Is it possible to register a http+domain-based URL Scheme for iPhone apps, like YouTube and Maps? .)

Unfortunately, this trick is not working in Android. We detect the device server side and wrote this instead of the itms: line:

"market://details?id=com.kaon.android.vstream";

Trouble is, whereas iOS throws an error when you go to an unhandled url scheme, Android goes to a generated page. Therefore, the timeout never gets a chance to run.

Is there some way on a web page to explicitly test for whether a custom URL scheme is handled, or can someone suggest a hack like this one that will work in Android? (Of course, I suppose I need a hack that's going to work no matter what browser they are using, which is probably a tall order...)

UPDATE: The approaches below do not work in Jelly Bean on a Nexus 7. The new Chrome browser does not go to a generated page (so the iFrame is not needed), but there does not appear to be any way to know whether the URL scheme was handled. If it was, the timeout fires anyway. If it wasn't handled the timeout fires. If I use an onload handler and an iframe, the onload handler never fires (whether the app is installed or not). I'll update if I ever figure out how to know whether the scheme was handled...

I've removed my "Solved" on my previous solution, since it doesn't work any more.

UPDATE 2: I have a good cross-platform solution now that works on iOS, Android 4.1 with Chrome, and Android pre-Chrome. See below...

Update 3: Google broke everything again with intents. Check out the VERY nice solution I've accepted by amit_saxena down there someplace /

Was it helpful?

Solution

Below is a working code snippet for most of the android browsers:

<script type="text/javascript">
    var custom = "myapp://custom_url";
    var alt = "http://mywebsite.com/alternate/content";
    var g_intent = "intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;end";
    var timer;
    var heartbeat;
    var iframe_timer;

    function clearTimers() {
        clearTimeout(timer);
        clearTimeout(heartbeat);
        clearTimeout(iframe_timer);
    }

    function intervalHeartbeat() {
        if (document.webkitHidden || document.hidden) {
            clearTimers();
        }
    }

    function tryIframeApproach() {
        var iframe = document.createElement("iframe");
        iframe.style.border = "none";
        iframe.style.width = "1px";
        iframe.style.height = "1px";
        iframe.onload = function () {
            document.location = alt;
        };
        iframe.src = custom;
        document.body.appendChild(iframe);
    }

    function tryWebkitApproach() {
        document.location = custom;
        timer = setTimeout(function () {
            document.location = alt;
        }, 2500);
    }

    function useIntent() {
        document.location = g_intent;
    }

    function launch_app_or_alt_url(el) {
        heartbeat = setInterval(intervalHeartbeat, 200);
        if (navigator.userAgent.match(/Chrome/)) {
            useIntent();
        } else if (navigator.userAgent.match(/Firefox/)) {
            tryWebkitApproach();
            iframe_timer = setTimeout(function () {
                tryIframeApproach();
            }, 1500);
        } else {
            tryIframeApproach();
        }
    }

    $(".source_url").click(function (event) {
        launch_app_or_alt_url($(this));
        event.preventDefault();
    });
</script>

You need to add source_url class to the anchor tag.

I have blogged more about it here:

http://aawaara.com/post/88310470252/smallest-piece-of-code-thats-going-to-change-the

OTHER TIPS

UPDATE: Google broke this. See the new accepted answer instead.

The key, it turns out, is the document.webkitHidden property. When you set window.location to a custom URL scheme and it opens, the browser keeps running, but that property goes to false. So you can test it to determine whether the custom URL scheme was handled.

Here's a sample, which you can view live

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Starting App...</title>
<script>

var URL = "kaonkaon://product.html#malvern;6";
var MARKET = "market://details?id=com.kaon.android.lepton.kaon3d";
var ITUNES = "itms://itunes.apple.com/us/app/kaon-interactive-3d-product/id525051513?mt=8&uo=4";
var QR = "http://goo.gl/gz07g"; // this should be a shortened link back to this page

function onLoad() {

    if (navigator.userAgent.match(/Android/)) {

        if (navigator.userAgent.match(/Chrome/)) {

            // Jelly Bean with Chrome browser
            setTimeout(function() {
                if (!document.webkitHidden)
                    window.location = MARKET;
            }, 1000);

            window.location = URL;

        } else {

            // Older Android browser
            var iframe = document.createElement("iframe");
            iframe.style.border = "none";
            iframe.style.width = "1px";
            iframe.style.height = "1px";
            var t = setTimeout(function() {
                window.location = MARKET;
            }, 1000);
            iframe.onload = function () { clearTimeout(t) };
            iframe.src = URL;
            document.body.appendChild(iframe);

        }

     } else if (navigator.userAgent.match(/iPhone|iPad|iPod/)) {

         // IOS
         setTimeout(function() {
             if (!document.webkitHidden)
                 window.location = ITUNES;
         }, 25);

         window.location = URL;

     } else {

         // Not mobile
         var img = document.createElement("img");
         img.src = "https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl="+encodeURIComponent(QR);
         document.body.appendChild(img);
     }
}
</script>
  </head>
  <body onload="onLoad()">
  </body>
</html>

This is the answer who will save you all !

https://developers.google.com/chrome/mobile/docs/intents

<a href="intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;end"> Take a QR code </a>

It will your url schestart me if the app is installed ortherwise it will start the market at the indicated package

@jesmith, this is a clean version that fixes double action on Android.

if (navigator.appVersion.indexOf('iPhone') > -1) {
  setTimeout(function noapp() { window.location="http://itunes.apple.com/app/id378890806?mt=8"; }, 25);
  window.location = 'vstream:';
}
else if (navigator.userAgent.indexOf('Android') > -1) {
  var iframe = document.createElement('iframe');
  iframe.style.visibility = 'hidden';
  iframe.src = 'vstream:';
  iframe.onload = function noapp() { window.location="market://details?id=com.kaon.android.vstream"; };
  document.body.appendChild(iframe);
}

Solved! The trick is to open my app in an IFRAME, instead of setting the location:

setTimeout(function() {
  window.location =
    "market://details?id=com.kaon.android.vstream";
}, 1000);

document.write('<iframe style="border:none; width:1px; height:1px;" src="vstream:view?code='+code+'"></iframe>');

Notice that I increased the timeout to 1000, because Android actually does both actions in every case (not ideal, but not awful), and this larger timeout is needed to make sure that Market doesn't end up being the thing the user sees when I'm already installed.

(And yes, of course using document.write is so last-century, but I'm old school that way :)

For some reasons, the final solution does not work for me on android (Is it just me?!!). The key is that iframe.onload function is NOT executed when your app is installed, and it IS executed when your app is NOT installed.

The solution becomes a little simpler actually. Here is the segment for "Older Android Browser" part:

    } else {

        // Older Android browser
        var iframe = document.createElement("iframe");
        iframe.style.border = "none";
        iframe.style.width = "1px";
        iframe.style.height = "1px";
        iframe.onload = function () { window.location = MARKET; };
        iframe.src = URL;
        document.body.appendChild(iframe);

    }

Embed a http server in your app as a Service, listen to a local port(higher than 1024, ex: 8080), send a request from Browser to 127.0.0.1:8080. If your app installed(and service running), start it, if request fail, goto google play.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top