Home > PhoneGap > PhoneGap – migrating iOS applications to Android (Part 1)

PhoneGap – migrating iOS applications to Android (Part 1)

In this post, I’ll be discussing the steps required to created an Android application using the PhoneGap framework. For those of you who don’t know, PhoneGap is an HTML5 app platform that allows you to author native applications with web technologies. So although we are generating a native Android application (APK file) that can be put on Google Play, the actual views of our application will be made up by web pages, embedded in your native app in a WebView.

PhoneGap promotes the idea of a single code-base (the part containing the html / javascript / stylesheets) where you use the PhoneGap platform to generate native packages for different mobile platforms like iOS, Android, …… There will always be a mobile platform specific part associated with a PhoneGap project, the native part, required to generate the mobile platform specific artifact (Android APK file, iOS IPA file,….)

The applications

Writing a fully featured cross platform app is beyond the scope of this particular blog post. Depending on the complexity of the app, and the target platforms that need to be supported, developing such an application can be very complex. In this blog post I will pick 2 very simple mobile webapps that were packaged using PhoneGap as native iOS applications. I’ll try to get a feel on the effort required to port them to the Android platform.

The idea behind this approach is to see how easy it is to have a truly cross-platform application with PhoneGap. After all, PhoneGap uses standard and common web technologies like html5 / css3 / jquery so it shouldn’t be that hard right ?

The holy grail of development, where you “write once” and “run anywhere”.

Unfortunately things aren’t always as simple as that. From my own personal experience, it has been always been more along the lines of “write once”, “try to run anywhere”, “tweak your writings to get it to run anywhere”.

I’ll be using 2 PhoneGap applications that were originally targetting iOS, and see what it takes to convert them to Android.

The source for this sample is still a work in progress, but you can already grab a copy on the PhoneGapAndroidConversion GitHub repository.

The development environment

I’m going to be using Eclipse as my IDE. There are no PhoneGap specific plugins to install, but we obviously require the Android SDK and the ADT plugin to be installed to facilitate Android development.

I’m adding all the steps that are required to setup a new PhoneGap Android Project. You can also clone my skeleton project for PhoneGap Android if you don’t wan to perform the manual steps below. It sets up a basic PhoneGap skeleton project for Android with a simple webview.

We’ll start by creating a standard Android Project.

We’ll be targeting the Android 4.0 platform (API level 14). This will allow us to configure hardwareAcceleration on the platforms that support it.

Hardware acceleration

In order to ensure that our application runs as smoothly as possible on Android, we’ll need to enable hardware acceleration where supported. The way hardware acceleration is enabled depends on the Android version you are running.

  • In Android 3.0 (Honeycomb API level 11) developers can choose to opt-in to the new hardware accelerated pipeline. It’s not enabled by default.
  • In Android 4.0 (API level 14), hardware acceleration is enabled by default for all applications

For applications running on lower API levels, you can turn it on by adding android:hardwareAccelerated=”true” to the tag in your AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ecs.phonegap.android.conversion"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:targetSdkVersion="14" android:minSdkVersion="7" />
	<uses-permission android:name="android.permission.INTERNET" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:hardwareAccelerated="true"
        android:debuggable="true">
....
    </application>
</manifest>

As we want to target as many devices as possible (including pre-Honeycomb devices) while still make use of the new hardwareAcceleration option, we’re going to set the targetSDK to level 14, and a minSdkVersion to 7 (Android 2.1).

The PhoneGap activity

We’ll let the Android Project wizard generate a default activity, so we already have that in place.

PhoneGap has an excellent Getting Started Guide where the different steps for the different mobile platforms are outlined. In case of Android, the following simple steps need to be executed.

  • Download PhoneGap from the PhoneGap website (direct link)
  • Extract the PhoneGap zip file somewhere on your filesystem.
  • Create an /assets/www folder in your Android project
  • Create a /libs in your Android project
  • Copy cordova-1.x.x.js from your android directory of your PhoneGap download earlier to /assets/www
  • Copy cordova-1.x.x.jar from your android directory of your PhoneGap download earlier to /libs
  • Copy xml folder from your PhoneGap download earlier to /res

For those that like to script this, just fill in the correct location of your eclipse project and phonegap download and execute the commands below :

export ECLIPSE_PROJECT=/Users/ddewaele/Projects/workspace/Room12PhoneGapAndroid
export PHONEGAP_DOWNLOAD=/Users/ddewaele/Projects/PhoneGap/phonegap-phonegap-de1960d
mkdir $ECLIPSE_PROJECT/assets/www
mkdir $ECLIPSE_PROJECT/libs
cp  $PHONEGAP_DOWNLOAD/lib/android/cordova-1.5.0.js $ECLIPSE_PROJECT/assets/www/
cp  $PHONEGAP_DOWNLOAD/lib/android/cordova-1.5.0.jar $ECLIPSE_PROJECT/libs/
cp -r $PHONEGAP_DOWNLOAD/lib/android/xml $ECLIPSE_PROJECT/res/

We’ll need to add the cordova-1.5.0.jar file to the build path of our Android project.

With that into place, our app looks like this :

Attaching the sources

If you want to get serious with PhoneGap development, having the PhoneGap sources attached to your project can help speed up debugging issues. The PhoneGap source code is made available through Apache.

This sample is available in Github with all the libraries and sources properly setup. If you’re using the sample from the git repository there is no need to execute the steps below. I’ve just added them here for informational purposes.

We start by cloning the git repository containing the PhoneGap source code :

git clone https://git-wip-us.apache.org/repos/asf/incubator-cordova-android.git

Cloning into incubator-cordova-android...
remote: Counting objects: 10357, done.
remote: Compressing objects: 100% (3032/3032), done.
remote: Total 10357 (delta 5819), reused 9451 (delta 5299)
Receiving objects: 100% (10357/10357), 19.32 MiB | 243 KiB/s, done.
Resolving deltas: 100% (5819/5819), done.
Davys-MacBook-Air:PhoneGap ddewaele$ ls -ltr
total 0

We then continue to switch to the current production tag, 1.5.0 :


cd incubator-cordova-android
git checkout tags/1.5.0
Note: checking out 'tags/1.5.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 8923e52... Adding support for legacy plugins

Now that we’ve checked out the code, we can zip up the PhoneGap sources (located in framework/src) in a file called cordova-src.zip

cd /Users/ddewaele/Projects/PhoneGap/incubator-cordova-android/framework/src
zip cordova-1.5.0-src.jar -r *

We’ll now attach this source file into our Eclipse project :

Starting the Room112 migration

We’ll start by checking out the original sources of the Room112 application. The sources are located in Github and can be checked out from the command line like this:

git clone https://github.com/jessefreeman/Room112

If you have XCode installed, you can click on the xcodeproj file and open the project in XCode. After launching it on the iPhone simulator, you should see something like this :

The bulk of this git repository contains XCode specific files, files we’re not interested in. We’re only interested in the web files, located in the www folder. There are however a couple of things we need to take into account :

  • The Room112 iOS sample uses PhoneGap v1.3.0, while we are going to use the latest version available at the time of writing, v1.5.0.
  • We’ve stumbled upon our first issue that prevents us from making this example truly cross-platform. The Room112 iOS code contains an iOS specific phonegap-1.3.0.js file that cannot be used on Android.
  • The Android specific phonegap javascript file (called cordova-1.5.0.js) is located in the PhoneGap distribution we downloaded earlier. It is also available in my skeleton Github repo.

The JSON bug

When we attempt to start the application for the first time on an Android 2.3.3 device, we are presented with the following screen :

Besides the fact that the background image is not correctly aligned, the major issue here is that our app crashes, failing to show the hotel card.

When we look at the Webconsole logs we see this :

04-02 22:11:15.066: E/Web Console(13752): Uncaught illegal access at file:///android_asset/www/js/lawnchair-0.6.1.min.js:8

The Room12 application uses a framework called Lawnchair as a replacement for html5 local storage. It promotes itself as being a lightweight, adaptive, simple and elegant persistence solution.

Unfortunately on our Android device, this is causing some problems. It’s not a problem with the framework as such, but rather related to a bug in the V8 JavaScript engine of the browser (WebView). The bug described in the Android issue tracker..

Certain Android devices are having issues when a JSON.parse(null) call is executed. Instead of returning null, as most devices do, we are confronted with an uncaught illegal access in the JSON object.

We can workaround this issue by putting the following code on top of our javascript :

JSON.originalParse = JSON.parse;

JSON.parse = function(text){
	if (text) {
		return JSON.originalParse(text);
	} else {
		// no longer crashing on null value but just returning null
		return null;
	}
}

Viewport properties

On my Android 4.0.4 device, I noticed the following errors in the Web Console when launching the app. It seems that hte preferred way of seperating viewport arguments is a “comma”. More information on this can be found in the CSS Device Adaptation.

04-03 00:30:51.265: E/Web Console(21509): Viewport argument value "device-width;" for key "width" not recognized. Content ignored. at file:///android_asset/www/index.html:5
04-03 00:30:51.265: V/Web Console(21509): Viewport argument value "1.0;" for key "initial-scale" was truncated to its numeric prefix. at file:///android_asset/www/index.html:5
04-03 00:30:51.265: V/Web Console(21509): Viewport argument value "0;" for key "user-scalable" was truncated to its numeric prefix. at file:///android_asset/www/index.html:5

Android Room112 screenshots

The Room112 application was specifically designed for the iPhone and the nature of the app makes it ao that it doesn’t render well on different screen resolutions.

By providing additional image resources we could optimizing the application for larger type screens. (although we won’t be covering that here.)


Samsung Galaxy S running Gingerbread 2.3.3


Samsung Galaxy Nexus running ICS 4.0.4


Samsung Galaxy Mini running Gingerbread 2.3.4


Asus TF101 Transformer running ICS 4.0.3

Starting the Employee Directory migration

The original Employee Directory source code is available here. The zip file only contains the www folder so you’ll need to create a new PhoneGap project in XCode, build it, setup the www folder, and replace the content of that www folder with the content found in the Employee Directory download. When launching it in the iPhone simulator, you should see something like this :

QueryString errors

In the Employee Directory sample, query strings are used to pass the ID of the selected employee to the detail page. This causes a problem on Android version 3.0 and up. The following error is being shown when we load up a page containing a query string.
The problem is described in this Android issue. The issue is fixed but marked for a “Future version”. We can only hope that it becomes available in the next Android point release (4.0.5) so that we can all benefit from fixes like that.

Using local storage as a workaround

As a quick workaround, we can try to use HTML5 local storage to pass data around instead of using query strings.

My initial workaround involved something like this when clicking on an employee :

function loadEmployeeDetail(id) {
	localStorage.setItem( "employeeId", id );
	location.href="employeedetails.html";
}

Unfortunately this doesn’t work, as it generated JSCallback errors on Android, again due to an Android issue.

04-04 22:20:53.379: I/Web Console(1523): JSCallback Error: Request failed. at :1194134965

In an effort to fix the error above, and as indicated by PhoneGap, a fix involved using the navigator.app object to load a URL (instead of using the location.href) :

function loadEmployeeDetail(id) {
	localStorage.setItem( "employeeId", id );
	//location.href="employeedetails.html"; // this doesn't work on Android (see )
	navigator.app.loadUrl("file:///android_asset/www/employeedirectory/employeedetails.html");
}

Although this fixed my problem on Android, it broke the iOS implementation.

Phonegap exposes a “device” object that we can use to detect the platform that we’re running on. In an ideal cross-platform-one-codebase world we wouldn’t be needing this, but in reality, you’ll need it whenever you want to do something specific for a particular platform.

A more robust way of handling the query string issue would be to use a different approach altogether, using a REST-style URL approach where employee details are fetched using employeedetails/1 instead of employeedetails.html?id=1. However, this would have a more fundamental impact on the original application and those changes are out of scope for this article. It just goes to show that what seems trivial at first (passing parameters from one page to another) can cause a lof of headaches when moving to different platforms.

Android Employee Directory screenshots

The Employee Directory renders correctly on all Android devices. Obviously, it is not optimized for tablet usage (lots of white space) as is the case with most of the mobile apps I’ve seen using web technologies.

Creating tablet optimized versions obviously requires additional work, as it involves developing different screens for different sizes. This is however also the case for native applications. Although it would be interesting to see how this should be tackled for a mobile app created with web technologies, it would require a dedicate article on its own.


Samsung Galaxy Nexus running ICS 4.0.4


Samsung Galaxy S running Gingerbread 2.3.3


Samsung Galaxy Mini running Gingerbread 2.3.4


Asus TF101 Transformer running ICS 4.0.3

Android point upgrades

A couple of weeks ago, while my phone was still running Android ICS 4.0.2, I posted on G+ that 2 featured cross-platform apps (Diary Mobile created using PhoneGap and GetoGrapher created using Sencha Touch) looked like stretched out iPhone apps.

Much to my surprise I noticed that after Android ICS 4.0.4 was rolled out to my phone, these apps started to look “normal” again, as can be screen in the screenshots below (granted, they still look like iPhone apps) :



This just goes to show that there can be a major impact on how your app looks and behaves, even between these maintenance point releases.

Hardware acceleration

I already talked a bit about hardware acceleration on Android, and how it can speed up animations. The Room 112 application uses an animation to slide the hotel room card onto the screen. With hardware acceleration disabled, the animation is horribly slow. Especially on ICS. Even Gingerbread (that has no hardware acceleration enabled) was faster than ICS. It wasn’t as smooth as on the iPhone, but acceptable. Only when we enabled hardware acceleration on ICS did we get the same level of performance as on the iPhone.

As we’ve already seen, hardware acceleration can be enabled/disabled on the application level (application tag in the manifest xml), as well as on the activity level. This is a good thing, as it allows you to tweak the settings right to the activity detail.

The Employee Directory uses an iScroll library in the employee list screen for enhanced scrolling on iOS. It seems that the iScroll library performs a lot slower when hardware acceleration enabled. In order to get a smooth result we had to run that screen without hardware acceleration. In that respect it’s nice to be able to disable hardware acceleration on the activity that is hosting that particular screen.

        <activity
            android:hardwareAccelerated="false"
            android:label="@string/employeedirectory"
            android:configChanges="orientation|keyboardHidden"
            android:name=".EmployeeDirectoryActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

Conclusions

These trivial sample apps show that it is not that straightforward to create a cross-platform app using a single codebase. Each platform (and different versions of the same platform) behave differently and often require their own dedicated tweaks and fixes. Although the scope of this first part was just to get a very basic version of these apps up and running on Android, we already stumbled upon several issues like :

  • Webview javascript engine issues – JSON.parse(null)
  • QueryString handling not working
  • Hardware acceleration tweaks

Keep in mind that this was a very limited exercise using relatively simple applications, where we didn’t take into account platform look and feel and proper CSS styling for multiple screen resolutions

It seems to be very difficult to find real-world examples of such multi-platform apps that follow good design, are feature-rich and are truly multi-platform. Out of the 7 featured apps on PhoneGap, only 2 run on both Android and iOS. The rest is targeting a single platform. The ones that are truly multi platform don’t run all that great. The ones that do work are usually very simple apps that could have just as well been mobile websites.

After spending some time looking and using those multi-platform frameworks, I’m not convinced yet. If you really want to deliver a high quality experience on multiple platforms, the effort required to get it right using web technologies cannot be under-estimated.

We are already seeing companies moving away from this approach and rewriting their apps in a native way.

On the other hand, one cannot argue the fact that web technologies like html5 / css3 are becoming increasingly important and they cannot be overlooked. They do provide several advantages over doing things natively and maintaining different code-bases. If your application is relatively simple, and you don’t care about platform look and feel affinity, they are definitely an option. For those type of applications it will be cheaper / easier to develop something using these technologies.

In a follow-up post, I’ll try to tackle the following topics :

  • the various CSS related issues that you need to take into account when working with multiple screen resolutions
  • how to organize your SCM for cross platform projects
  • use some of the PhoneGap APIs to access the device capabilities, and see how they translate in a multi-platform setup

References

 

Inspiration for this article

Sources

Hardware acceleration

Tools

Misc libraries / frameworks used in the samples

Android issues

Bookmark and Share
  1. April 6th, 2012 at 21:24 | #1

    instead of porting an app I reccomend building from the begining on more than one platform; yes there are differences between them. addressing them in the flow of development is far easier than direct port.

    • admin
      April 6th, 2012 at 22:02 | #2

      I agree, however a lot of people seem to think that simply by choosing a cross-platform framework / product, they’ll be able to do the whole write-once run-anywhere thing without any effort. These trivial examples were used to show that there is a lot more to it than that. I also noticed that a lot of apps using these frameworks start off by simply supporting one particular platform (ex: iOS), and get ported to other platforms at a later stage.

  1. No trackbacks yet.