Monthly Archives: July 2014

Upgrading Cordova-Android apps outside Google Play Store with angularjs

Recent months I’ve working with enterprise mobile applications. This apps are’t distributed using any marketplace, so I need to handle the distributions process. With Android you can compile your apps, create your APK files and distribute them. You can send the files by email, use a download link, send the file with bluetooth, or whatever. With iOS is a bit different. You need to purchase one Enterprise license, compile the app and distribute your IPA files using Apple’s standards.

OK, but this post is not about how to distribute apps outside the markets. This post is about one big problem that appears when we need to upgrade our apps. How do the user knows that there’s a new version of the application and he needs to upgrade? When we work inside Google Play Store we don’t need to worry about it, but if we distribute our apps manually we need do something. We can send push notifications or email to the user to inform about the new version. Let me show you how I’m doing it.

My problem isn’t only to let know to the user about a new version. Sometimes I also need to ensure that the user runs the last version of the app. Imagine a critical bug (solved in the last release) but the user don’t upgrade.

First we need to create a static html page where the user can download the APK file. Imagine that this is the url where the user can download the last version of the app:

http://192.168.1.1:8888/app.apk

We can check the version of the app against the server each time the user opens the application, but this check means network communication and it’s slow. We need to reduce the communication between client and server to the smallest expression and only when it’s strictly necessary. Check the version each time can be good in a desktop application, but it reduces the user experience with mobile apps. My approach is slightly different. Normally we use token based authentication within mobile apps. That’s means we need to send our token with all request. If we send the token, we also can send the version.

In a angular app we can define the version and the path of our apk using a key-value store.

.value('config', {
        version: 4,
        androidAPK: "http://192.168.1.1:8888/app.apk"
    })

Now we need to add version parameter to each request (we can easily create a custom http service to append this parameter to each request automatically, indeed)

$http.get('http://192.168.1.1:8888/api/doSomething', {params: {_version: config.version}})
    .success(function (data) {
        alert("OK");
    })
    .error(function (err, status) {
        switch (status) {
            case 410:
                $state.go('upgrade');
                break;
        }
    });

We can create a simple backend to take care of the version and throws an HTTP exception (one 410 HTTP error for example) if versions doesn’t match. Here you can see a simple Silex example:

<?php

include __DIR__ . "/../vendor/autoload.php";

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;

$app = new Application([
    'debug'   => true,
    'version' => 4,
]);

$app->after(function (Request $request, Response $response) {
    $response->headers->set('Access-Control-Allow-Origin', '*');
});

$app->get('/api/doSomething', function (Request $request, Application $app) {
    if ($request->get('_version') != $app['version']) {
        throw new HttpException(410, "Wrong version");
    } else {
        return $app->json('hello');
    }
});

$app->run();

As you can see we need to take care about CORS

With this simple example we can realize if user has a wrong version within each server request. If version don’t match we can, for example redirect to an specific route to inform that the user needs to upgrade the app and provide a link to perform the action.

With Android we cannot create a link to APK file. It doesn’t work. We need to download the APK (using FileTransfer plugin) and open the file using webintent plugin.

The code is very simple:

var fileTransfer = new FileTransfer();
fileTransfer.download(encodeURI(androidUrl), 
    "cdvfile://localhost/temporary/app.apk",
    function (entry) {
        window.plugins.webintent.startActivity({
            action: window.plugins.webintent.ACTION_VIEW,
            url: entry.toURL(),
            type: 'application/vnd.android.package-archive'
        }, function () {
        }, function () {
            alert('Failed to open URL via Android Intent.');
            console.log("Failed to open URL via Android Intent. URL: " + entry.fullPath);
        });
    }, function (error) {
        console.log("download error source " + error.source);
        console.log("download error target " + error.target);
        console.log("upload error code" + error.code);
    }, true);

And basically that’s all. When user self-upgrade the app it closes automatically and he needs to open it again, but now with the correct version.

Advertisements

Testing Phonegap/Cordova applications fast as hell in the device (with ionic framework)

Normally when we work with Phonegap/Cordova applications we work in two phases. First we develop the application locally using our browser. That’s “fast” phase. We change something within our code, then we reload our browser and we see the outcome. It isn’t different from a “traditional” web developing process. Normally I use the ionic framework. Ionic is great and it also provides us a good tool to run a local server. We just type:

ionic serve

And ionic starts a local server on port 8100 with our Cordova application, ready to test with the browser (it also opens the browser). That’s not the cool part. Ionic also starts a live reload server at http://0.0.0.0:35729 and adds the following snippet at the end of our index.html

<script type="text/javascript">//<![CDATA[
document.write('<script src="' + (location.protocol || 'http:') + '//' + (location.hostname || 'localhost') + ':35729/livereload.js?snipver=1" type="text/javascript"><\/script>')
//]]></script>

With this snippet our application will be reloaded when we add/remove something in our file tree (it runs a filesystem watcher in background).

But as I said before it’s the “fast” phase and sooner or later we will need to run the application in the real device. OK we’ve got emulators, but they are horrible. Android emulator is incredible slow. IOS one is faster but we need to redeploy the application again and again with each change. For example when we correct a silly bug we need to run the following command to see the application running on the device:

cordova run android --device

And it takes time (around 10 seconds). We’ve gone from the “fast” phase to the “slooooow” one. That means that I tried to avoid this phase until no remedy.

If you don’t use plugins you can let this “slow” phase to the end, only to see the behaviour in the device and fix customizations, but ir we use plugins (camera plugin, push notifications or things like that) we really need to test on the real device. Those kind of things doesn’t work in the browser or even with the emulator.

This “slow” phase droves me crazy, so I started to think a little bit about it. One Cordova app has two parts. The native one (java code in android and objective-c in ios) and the html/js part. We need to tell to our Cordova application where is the initial index.html. We usually do it in config.xml

<content src="index.html" />

But we can change this initial file and use a remote one. That’s the way to create a “native” app from and existing web application.

<content src="http://gonzalo123.com" />

According to this we can start a local server in our host and use this local web server. Even in our LAN (if our android/ios device is the LAN of course)

<content src="http://192.168.1.1:8100/index.html" />

But, what happens with the plugins? Plugins needs cordova.js file and this file isn’t in www folder. This file is generated when we build the application to a specific platform

platforms/android/assets/www/cordova.js

So, what’s the idea. The idea is:

  • Run a local server with (inoic serve for example)
  • Enable the fs watcher to restart the application when we change one file in the filesystem (inonic serve do it by default)
  • Build the application and install it in the real device
  • Use our local server to serve static files instead of build again and again the application with each change.

With this approach we only need to deploy the application to the real device when we want to add/remove one plugin. If we change anything in the static files (html, js, css) our app will be reloaded automatically. The “slow” phase turns into a “fast” phase.

How can we do it? It’s easy. In this example I suppose that we’re using one android device. If we use on iPhone we only need to change “adroid” to “ios”.

First of all we need to prepare our index.html to enable auto-reload. “ionic serve” do it automatically but it thinks that we’re going to use it with your host browser. Not with the “real” device. We can change it manually adding to our index.html (this snippet suppose that your host is 192.168.1.1 if it’s a different one use your local IP address):

    <script type="text/javascript">//<![CDATA[
    document.write('<script src="http://192.168.1.1:35729/livereload.js?snipver=1" type="text/javascript"><\/script>')
    //]]></script>

Now we change our config.xml to use our local server instead of device’s files:

<!--<content src="index.html" />-->
<content src="http://192.168.1.105:8100/index.html" />

Now we need to deploy the application to our device:

cordova run android --device

Each time we add/remove one plugin we need to redeploy to the device. But we need to keep in mind that our device will use the cordova.js from our local server, and not from its filesystem. “cordova run android –device” will generate the file to the platform and deploy them to the real device, but as well as we’re going to use this file from our local server (in www), we need to create a set of symlinks in our www folder.

(I’ve got one setUp.sh file with this commands)

cd www
ln -s ../platforms/android/assets/www/cordova.js
ln -s ../platforms/android/assets/www/cordova_plugins.js
ln -s ../platforms/android/assets/www/plugins
cd ..

Now can start the application’s server in our host with:

ionic serve --nobrowser

notice that we’re using –nobrowser. We’re using this parameter to not to open our local browser. We’re going to use de device’s Cordova’s Webkit one, and also if we open our browser it will crash because cordova.js is present now and our local host isn’t a real device.

Each time we need to redeploy the application to the device (new plugin for example) we need to remember to quit the symlinks, and redeploy.

(I’ve got one tearDown.sh file with this commands)

rm www/cordova.js
rm www/cordova_plugins.js
rm -Rf www/plugins 

And that’s all. I now that this little hack may looks like something difficult but we need less than a minute to set up the environment and we will save thousand of seconds in the development process. I we work a little bit we can automate this process and turn it into a trivial operation, but at least now I feel very comfortable.

Of course you need to remember to clean the project when you finish and use the device’s files. So we need to remove the auto-reload snippet in the index.html, remove symlinks and restore config.xml.

UPDATE:

ionic framework comes now with this feature out-of-the box. Just follow the instructions explained here:
http://ionicframework.com/blog/live-reload-all-things-ionic-cli/