Using Custom Events in Android Apps

There are two major concepts that provide a foundation for building scalable, native Android applications: loosely coupling requests and responses via events, and moving CPU intensive operations off the main thread. This post looks at events. It seems the online documentation on putting together all the pieces of  using custom events is sparse, at best. I’ll show you one example of how to put all the pieces together to create, dispatch and listen for custom events.  The end result is you will be able to decouple your application and make them more flexible and much less likely to break.

I’ve talked about event-based architectures before, and I think they are even more important when building applications for devices. They give you the ability to take into account processor delays and inconsistent internet connections. Between the three pieces of working with custom events described below you should be able to get up and running pretty quickly. I don’t go into much detail on what’s inside the various example as you can find those individual details by searching for them. It’s putting it all together that’s typically the hardest part.

The Event Class. Here’s an example of the content of an Event Class. I’ve taken this several steps further than most examples to include showing how to handle multiple Event types, which are accessed in this example as enums, and implementing multiple listeners. You can also extend your listeners with additional information of just about any type. In this example, I show using String’s for passing messages, but you could just as easily use custom Objects. This should give you a more realistic example of what goes into a commercial application.

	public class MapViewControllerEvent extends EventObject{

		private static final long serialVersionUID = 1L;

		public MapViewControllerEvent(Object source){
			super(source);
		}
	}

	public enum MapViewEvent{
		/**
		 * Connection to the internet has been lost.
		 */
		CONNECTION_LOST("Connection to the internet has been lost."),
		/**
		 * Connection to the internet has been restored.
		 */
		CONNECTION_RESTORED("Connection to the internet has been restored"),
		/**
		 * The LocationService has shutdown. Check logcat for errors.
		 */
		LOCATION_EXCEPTION("There was an unknown error related to the LocationService"),
		/**
		 * Indicates the LocationService is initialized and ready.
		 */
		LOCATION_INITIALIZED("The LocationService has been initialized. NOTE: it still may fail after a start() attempt."),

		private String description;
		private MapViewEvent(String description){
			this.description = description;
		}
	}

	public interface MapViewControllerEventListener extends EventListener{
		/**
		 * Indicates there has been a location change received from LocationService.
		 * @param event
		 * @param message
		 */
		public void onLocationChangeEvent(MapViewControllerEvent event,String message);
		/**
		 * Indicates whether or not device has internet connectivity.
		 * @param event
		 * @param message
		 */
		public void onConnectionChangeEvent(MapViewControllerEvent event,String message);
	}

	protected EventListenerList eventListenerList = new EventListenerList();

	/**
	 * Adds the eventListenerList for MapViewController
	 * @param listener
	 */
	public void addEventListener(MapViewControllerEventListener listener){
		eventListenerList.add(MapViewControllerEventListener.class, listener);
	}

	/**
	 * Removes the eventListenerList for MapViewController
	 * @param listener
	 */
	public void removeEventListener(MapViewControllerEventListener listener){
		eventListenerList.remove(MapViewControllerEventListener.class, listener);
	}

	/**
	 * Dispatches CONNECTION and LOCATION events
	 * @param event
	 * @param message
	 */
	public void dispatchEvent(MapViewControllerEvent event,String message){
		Object[] listeners = eventListenerList.getListenerList();
		Object eventObj = event.getSource();
		String eventName = eventObj.toString();
		for(int i=0; i<listeners.length;i+=2){
			if(listeners[i] == MapViewControllerEventListener.class){
				if(eventName.contains("CONNECTION"))
				{
					((MapViewControllerEventListener) listeners[i+1]).onConnectionChangeEvent(event, message);
				}
				if(eventName.contains("LOCATION")){
					((MapViewControllerEventListener) listeners[i+1]).onLocationChangeEvent(event, message);
				}
			}
		}
	}

Setup Listeners. Here’s how to set up the listener in your Activity. As an example, I called my Event Class MapViewController, but you can name it anything you like:

_mapViewController = new MapViewController(this);
_mapViewController.addEventListener(new MapViewControllerEventListener() {

	@Override
	public void onLocationChangeEvent(MapViewControllerEvent event,
			String message) {
		String eventName = event.getSource().toString();
		if(eventName.contains("EXCEPTION")){
			//TODO let user know
		}
		else{
			//TODO push UX change
		}

	}

	@Override
	public void onConnectionChangeEvent(MapViewControllerEvent event,
			String message) {
		// TODO Auto-generated method stub

	}
});

Dispatch Event. And, here’s how to dispatch an Event:

dispatchEvent(new MapViewControllerEvent(MapViewEvent.LOCATION_INITIALIZED),"Attempting to start location service.");

EventListenerList Class. For some reason, which I would characterize as a major oversite, Android does not currently provide a public EventListenerList Class. This Class makes creating custom events so much easier. However, the folks of the Firefly Client project for Android saved the day and were kind enough to create and post one publicly under an Apache License. The code is a bit dated, and shows some minor warnings when building Android v4, but it will work just fine. You’ll need to include a copy of this Class in your project to make things work.

So, that’s pretty much it. I hope this info helps you build better and more succesful projects.

References:

Java Tutorial – General Information about Writing Event Listeners

Firefly Client Android – EventListenerList Source Code

[Edited: June 7, 2012 – fixed various minor typos]

Handle null values when using Android’s LocationManager

I’d noticed a number of fatal app crashes recently, and it was only through luck I discovered that I wasn’t handling null values that were being returned by the device’s  LocationManager. I only discovered this through a bit of detective work and being lucky enough to have the app crash while hooked up to logcat when the device decided to act up. I can now say with certainty that it’s true a GPS_PROVIDER or LOCATION_PROVIDER can indeed return null values. So, as a best practice  now I simply listen for them and handle them gracefully.

I’ve also noticed that sometimes these null values can happen sporadically, so I’ve also implemented a counter system that says if a certain number of null values happen in a row then shut down the LocationManager and notify the user, otherwise simply ignore them. Here’s an example of the basic pattern:

// Acquire a reference to the system Location Manager
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

// Define a listener that responds to location updates
LocationListener locationListener = new LocationListener() {

	int counter = 0;

	public void onLocationChanged(Location location) {
      // Called when a new location is found by the network location provider.
		if(location != null){
			counter = 0
			double lat = loc.getLatitude();
			double lon = loc.getLongitude();
		}
		else
		{
			counter++;
			if(counter > 3)
			{
				// Remove the listener you previously added
				locationManager.removeUpdates(locationListener);
                                //locationManager = null;

				//Let user know there was a problem and that GPS was shut off....
			}

		}
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {}

    public void onProviderEnabled(String provider) {}

    public void onProviderDisabled(String provider) {}
};

// Register the listener with the Location Manager to receive location updates
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);

Here’s one example of a GPS problem on a Motorola Atrix in logcat:

06-01 15:15:43.298: E/libgps(1653): recv_command_nmea() : NMEA_PARSE_ERROR_DATA_FIELDS_MISSING: The sentence does not contain enough fields for its data type

7 Critical Things to Know When Building Any Mobile App

This blog post builds on concepts proposed in an earlier post about not all mobile apps being created equally. If you are a developer who is in the process of migrating to mobile this post is for you. It’s intended to raise awareness of important items to consider in your requirements. My goal is to help you identify some of the major gotchas early on in the development process and improve your chances for success.

There are many more details to learn on the topics I’ve described below. The good news is that in the last few years the amount of deeply helpful documentation has expanded considerably. Where possible I’ve tried to include links related to each topic.

Touch-based Workflows. Recent research has shown that people use their smartphones more often than web apps, and they spend roughly 80% of their time on social media and games. Because of this and the fact that smartphones today are touch driven and not mouse driven, you have to take that into account in your user interface design. Touch implies many things including gestures and multi-touch. You can toss your old conceptions of user interface design based on desktops and tablets, and check out Android’s recommendations as well as Apple’s. My strong recommendation is to hire a UX designer to help you through building a user interface.

Mutliple form factors come with various screen sizes and densities. Long gone are the days of building for just three main browser types. Now you have to take into consideration iPhones, iPads, tablets, numerous different style androids as well as desktop and laptops. Android defines the following screen sizes and, as you can see, this is quite varied and smaller than a typical laptop or desktop. Those typically run 1024 x 768 or greater.

  • xlarge screens are at least 960dp x 720dp
  • large screens are at least 640dp x 480dp
  • normal screens are at least 470dp x 320dp
  • small screens are at least 426dp x 320dp

This is important to know because an app that looks good on an iPad may not look good, or display correctly, on the four inch display of a Motorola Atrix at 960 x 540. A button that looks correctly sized on one smartphone may look too big on another. A whopping 84% of all Android screens are what Android defines as normal size (>=  470dp x 320dp) and between either medium dpi (~160dpi) or high dpi (~240dpi). But, you still have to take into consideration other densities. I also recommend taking a look at new HTML5 browser-based technologies to help with addressing this problem, such as CSS media queries.

Inconsistent Internet. It’s a best practice to check if internet connections exist and gracefully handle HTTP requests when the internet is down, as I blogged about here.  Depending on your application and needs, you should also monitor whether or not a wireless connection can be made and then allow the application to switch to wireless where possible. Wireless also has the advantage of using less battery power.

Slower Connections. And, on a related note, you can’t always depend on 4G connections having consistent maximum download speeds. Over the course of a user session, the connection speed will vary widely and you should plan for that. I’ve been trying to find some stats on mobile internet quality world-wide, if they are out there they are hard to find. But, we’ve all experience spotty mobile internet coverage. Take this into account if you are transferring large amounts of data between your servers and your app. You should also consider detecting when the user is in an area of greater bandwidth and use that to download more data less often. Use loosely coupled and event driven architectures. Test app load times on various devices and around town and away from your office.

Less CPU Horsepower. While the latest generation of four core phones are certainly the most powerful phones yet. In general, applications and web pages will run slower on phones than they do on your development machine running a desktop browser. Take older generation phones into account because they are usually significantly slower than the newer phones. There are a few workarounds in HTML5 to help with this, in that done correctly they can offload rendering to the hardware. In native applications be aware of memory leaks because, remember, more memory usage means less battery life and applications that can run slower over time.

Support across multiple operating system versions. Remember on Android that the vast majority of users are still running v2.2 – v2.3.7 even though v4.x is currently shipping. You’ll have to do some research on your target market and find out what versions and type of phones they are using. You can’t support everything, but you can make educated guesses. Apple, on the other hand, has a significantly more limited selection of phones and tablets that you have to support, and they do a great job helping you support those.

There are some solutions that help with building cross-platform mobile apps, to go into more detail will take another blog post. Here’s a few: Adobe Flex, PhoneGap and Titanium. Keep in mind that the future of Flex, as a development platform, is being called into question after Adobe open sourced everything but the browser and desktop runtimes to the Apache Foundation. PhoneGap and Titanium offer what is now being called “hybrid” solutions where you can build an application in JavaScript, for example, and then compile that code for native deployments on Android and iOS.

Battery Life. Ah, battery life is last but certainly not least. Be aware of how battery intensive your application is and try to minimize battery consumption as much as possible. The Android online docs have a number of highly information articles on this subject. Smaller app footprint in memory means less battery consumption. Heavy CPU usage means more battery usage. Minimize GPS usage through smart algorithms to help preserve battery life.  Switch to 802.11 wireless connections where possible, since this requires less battery power than 3G and significantly less power than 4G.

So, there you go. I hope these suggestions help. If you have more suggestions based on your own experience please post a comment!

References:

Android Gestures

Android Optimize Battery Life

Android Screen Sizes and Densities

CSS media queries

Android UI Design

Android Model for Best GPS Performance

iOS User Experience

HTML5 Hardware Acceleration

Event-based Architectures for Adobe Flex