In support of beta releases for Android applications

Much has been said about testing your Android applications on several different devices , and several different services exist to test deployment of your application if you want to make sure it runs on certain devices. But in my opinion, nothing beats releasing beta Android applications on Google Play.

To illustrate what I mean, a couple of days ago we released a version that had code similar to this:

public boolean shouldOverrideUrlLoading(WebView __view, String __url) {
	String myPrefix = "internal://getDataAndDoSomething?data="
	if (__url.substring(0, myPrefix.length()).equals(myPrefix)) {
		// Called fake protocol, so grabs data instead
		String data;
		try {
			data = URLDecoder.decode(__url.substring(myPrefix.length()), "UTF-8");
			doSomethingWithTheData(data);
		} catch (UnsupportedEncodingException __e) {
		}
		return true;
	}

	return super.shouldOverrideUrlLoading(__view, __url);
}

This Java function intercepts URL calls executed on an Android WebView, such as when the user taps on a link to a different page. It is displaying a normal web page that may have a link to a hard-coded URL – the one that starts with “internalProtocol://”. Thus, when the user taps links to that URL, my code should grab the command and data passed, do something with it, and then return true – meaning that the WebView itself won’t try to do anything with it, like opening a new page. The tap is essentially intercepted by my code (taps on all other links should behave normally; this same WebView is used for visiting normal websites).

Can you spot the problem with the code?

One day after the application was released in the wild, I started seeing a few error reports on Google Play’s Developer Console (in case you don’t know, Android applications normally send a crash report to this console, including a stack trace of the error). They looked like this:

java.lang.StringIndexOutOfBoundsException: length=31; regionStart=0; regionLength=37
	at java.lang.String.startEndAndLength(String.java:593)
	at java.lang.String.substring(String.java:1474)
	at com.mypackage.MyApplication.activities.web.WebActivity$2.shouldOverrideUrlLoading(WebActivity.java:170)
	at android.webkit.CallbackProxy.uiOverrideUrlLoading(CallbackProxy.java:231)
	at android.webkit.CallbackProxy.handleMessage(CallbackProxy.java:331)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loop(Looper.java:137)
	at android.app.ActivityThread.main(ActivityThread.java:4697)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:511)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:787)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:554)
	at dalvik.system.NativeStart.main(Native Method)

Can you spot the problem now? It made me feel really stupid once I saw the stack trace.

The problem is that line 3 – the one that checks whether the URL starts with the internal prefix String – is trying to compare the prefix with a String of a similar length extracted from the URL. If the URL is shorter than the prefix, however, it will fail, since there are not enough characters to satisfy the substring() call (to my defense, I wrote the code in a hurry, trying to replicate a pattern I had from my ActionScript days).

The fix, of course, was simple. The somewhat convoluted prefix check above can be replaced with this new line 3:

public boolean shouldOverrideUrlLoading(WebView __view, String __url) {
	String myPrefix = "internal://getDataAndDoSomething?data="
	if (__url.startsWith(myPrefix)) {
		// Called fake protocol, so grabs data instead
		String data;
		try {
			data = URLDecoder.decode(__url.substring(myPrefix.length()), "UTF-8");
			doSomethingWithTheData(data);
		} catch (UnsupportedEncodingException __e) {
		}
		return true;
	}

	return super.shouldOverrideUrlLoading(__view, __url);
}

This is a problem you would hardly catch on any testing – no matter the number of devices you have been testing. It was made possible by having someone testing the application in the wild and an error being reported by the developer console (and also only because some specific users of this application were redirected to a very short URL based on their account name).

I wrote the fix and released an update to the application – with other fixes and features rolled in – the same day.

This may not be the best release approach for all applications; some times, it makes more sense to have a very solid release rather than expecting your users to act as beta-testers during the first couple of weeks or so of an application release (since you risk alienating users that run into showstopping bugs). However, if there’s something to be said about the Google Play publishing model, is that it offers developers easy discoverability of errors – or at least, of errors that generate actual crashes – and fast reaction times for fixes. And while the number of different devices and configurations one has to test may negatively impact development time, it’s good to know that some more positive features are in place too. This is some times overlooked when compared the Android ecosystem to the other players in the mobile field.