Monday, August 8, 2011

How to Program Google Android



So you saw the Android announcement and decided you wanted a piece of that US$10million in prize money huh? In the week since the SDK was released more than 4,300 people have joined the Android support forum posting more than 4,000 messages between them. Robert Scoble doesn't know a single developer playing with Android – perhaps Scoble doesn't hang around with many developers?

I wanted to give the SDK a good work-out so my application uses the GPS, the address book, and has a map.

The only way to judge an SDK is getting in there and writing an application that does something cool, so I'll take you through the development process for my first Android application: Where Are My Friends?

WamF shows how far away you are right now from all the people in your address book, plots you and them on a map, and draws lines between you and any friends nearby.

WamF is pretty simple but it makes use of some of the more interesting features of Android – Location Based Information (GPS etc), maps, the contacts manager, and the phone dialer. Total development time from hitting Download on the SDK page was about 14 hours (spread over two mornings and evenings). My Android development is in Windows with Eclipse using the plugin, so I will assume you're doing the same.

Before I get started here's a bit on my background. I've mentioned before that I'm a C# .NET desktop applications developer in my real life. It's been almost 10 years since I've done anything with Java and I've never done any mobile phone development. With Android I develop in Windows with Eclipse using the Android plugin.

Let's start by downloading Eclipse and unzipping it into a new folder. Then download the Android SDK and unzip that into another new folder. Open Eclipse and create a new workspace for Android development.

Install the Android Plugin by selecting Help > Software Updates > Find and Install..., and in the dialog box choose Search for new Features to install. Select New Remote Site and enter https://dl-ssl.google.com/android/eclipse/. into the dialog box. Hit OK and accept all the prompts until it's installed. Restart Eclipse and you're almost ready to rock.

Select Window > Preferences... and select Android, then put the folder where you unzipped the SDK into the SDK Location text box. Hit Apply then OK and you're done.

The tutorial and exercises are useful. Do them.

Let's make sure everything's installed right by creating the Hello Android demo. The Android team have a detailed description of how to do this so I won't repeat it here. It's worth checking out the known bugs if you encounter any problems.

The Android documentation is excellent; after you've finished the Hello Android project run through the exercises. They're easy to follow and give a good idea of how a 'real' application fits together.

Design a UI, leveraging one of the sample projects

Onto business. Step one should always be UI design. Figure out what it is you want to tell the user and what actions they'll need then develop an interface that will make this as intuitive as possible. To keep things simple I'm going to base my new project on the NotePad project used in the tutorial exercises.

I'll start by changing the resource strings to change the name of the app, and modifying the menu options .

Use the Location Based Services to figure out where we are and request updates when we move

Possibly the most enticing of the Android features are the Location Based Services that give your application geographical context through Location Providers (GPS etc). Android includes a mock provider called 'gps' that marches back and forth through San Fransisco. Alternatively you can create your own mock providers in XML.

You use the LocationManager to find your current position.

    locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); Location location = locationManager.getCurrentLocation("gps");

Iterate over the address book pulling out names, locations, and phone numbers

A less publicized feature of Android is the ability to share content between applications. We're going to use this feature to populate our List with our contacts' names and their current distance from our phone so we create an updateList method that we call after we've gotten our current location.

Use the ContentResolver to return a query that provides access to data shared using Content Providers. Queries are returned as cursors that provide access to the underlying data tables. The data we're interested in is accessed using the People content provider.

    Cursor c = getContentResolver().query(People.CONTENT_URI, null, null, null, null); startManagingCursor(c);

The Cursor is a managed way of controlling your position (Row) in the underlying table. We get access to the data by specifying the column that holds the information we're after. Rather than memorising the column index for each Content Provider we can use constants from the People class as a shortcut.

    int coordIdx = c.getColumnIndex(People.NOTES); int phoneIdx = c.getColumnIndex(People.PhonesColumns.NUMBER); int nameIdx = c.getColumnIndex(People.NAME);

Now iterate over the table using the cursor storing the results in arrays. You'll note that we're pulling our contacts' location from the Notes field. In reality we'd want to figure this out based on their address using a geocoding lookup.

    List listItems = new ArrayList();



    c.first();

    do {
      String name = c.getString(nameIdx);

      String coords = c.getString(coordIdx);

      String phone = c.getString(phoneIdx);



      ... [ Process the lat/long from the coordinates ] ...

      ... [ Storing their location under variable loc ] ...



      String distStr = String.valueOf(location.distanceTo(loc)/1000);

      name = name + " (" + distStr + "km)";

      listItems.add(name);



      numbers.add("tel:" + phone);
    } while(c.next());

Then we assign our list of strings to the array using an ArrayAdapter.

    ArrayAdapter notes = new ArrayAdapter(this, R.layout.notes_row, items);

    setListAdapter(notes);

Refresh our list when we move

Given the location sensitive nature of WamF it makes sense to update the display whenever we move. Do this by asking the LocationManager to trigger a new Intent when our location provider notices we've moved.

    List providers = locationManager.getProviders();

    LocationProvider provider = providers.get(0);

    Intent intent = new Intent(LOCATION_CHANGED);

    locationManager.requestUpdates(provider, minTime, minDistance, intent);

Intents in Android are like events in traditional event driven programming, so we're triggering a LOCATION_CHANGED event/intent every time we move by a minimum distance after a minimum time. The next step is to create an IntentReceiver (event handler), so create a new internal class that extends IntentReceiver and override the ReceiveIntent event to call our update method.

    public class myIntentReceiver extends IntentReceiver {
      @Override

      public void onReceiveIntent(Context context, Intent intent) {
        updateList();
      }
    }

We then have our activity listen for a LOCATION_CHANGED intent by registering the event handler and specifying the intent it should be listening for (LOCATION_CHANGED). Do this in the onCreate method or create a new menu option to start/stop the automatic updates.

    filter = new IntentFilter(LOCATION_CHANGED);

    receiver = new myIntentReceiver();

    registerReceiver(receiver, filter);

Keep your phone running light by registering / unregistering the receiver when the activity Pauses and Resumes – there's no point in listening for location changes if we can't see the list.

Set up a map activity and create an overlay to show where you are in relation to your friends

Half of the fun in having location sensitive information is drawing it on a map. Create a new activity class to display a map centered on our current location with markers at our friends locations. While we're at it we can draw a line from our position to each of our friends.

The map control itself is called a MapView, but we can only use a MapView in a MapActivity, so we'll change the inheritance of this activity to MapActivity.

    public class MyMapViewActivity extends MapActivity

To display the map we need to create a new MapView and set it as the content for our activity in the OnCreate method.

    MapView mapView = new MapView(this);

    setContentView(mapView);

This will make the MapView fill the entire screen, so use views like LinearLayout if we want to create a more complicated UI layout.

We'll want to get access to the OverlayController and MapController, so create global variables to store them and assign the references within the OnCreate method. We'll also be using the Location information, so get a reference to that too. With the references assigned set your map zoom and starting location using the MapController. When you're finished OnCreate should look something like this.

    protected void onCreate(Bundle icicle) {
      super.onCreate(icicle);

      MapView mapView = new MapView(this);

      mapController = mapView.getController();

      overlayController = mapView.createOverlayController();

      locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

      mapController.zoomTo(9);

      setContentView(mMapView);

      updateView();
    }

updateView is where we do the work. Start by getting our current location and convert the Lat/Long to a map Point, then centre the map on our current location.

    Double lat = location.getLatitude()*1E6;

    Double lng = location.getLongitude()*1E6;

    Point point = new Point(lat.intValue(), lng.intValue());

    mapController.centerMapTo(point, false);

The only thing left to do on our map is draw markers and link them up with lines. To do this you need to create a new class that extends Overlay, and add this using the OverlayController.

    MyLocationOverlay myLocationOverlay = new MyLocationOverlay();

    overlayController.add(myLocationOverlay, true);

The work in the Overlay class is done by overriding the draw method.

    protected class MyLocationOverlay extends Overlay {
      @Override

      public void draw(Canvas canvas, PixelCalculator calculator, boolean

      shadow) {
        ... [ draw things here ] ...
      }
    }

I start by drawing a 'marker' on my current location. There doesn't seem to be support for 'traditional' Google Maps markers but you can achieve the same thing by drawing on the map canvas; I chose to draw small circles as markers. First you need to use the PixelCalculator to convert your Lat/Long points to screen coordinates, then create a Paint object to define the colours and settings for your brush. Then paint your markers.

    int[] screenCoords = new int[2];

    calculator.getPointXY(point, screenCoords);

    RectF oval = new RectF(...);

    Paint paint = new Paint();

    paint.setARGB(200, 255, 0, 0);

    canvas.drawOval(oval, paint);

I add my friends locations the same way as before, iterating over my address book grabbing names and locations. I filter out anyone too far away (say 10km) and draw markers, names (drawText), and joining lines (drawLine) to those nearby.

Let's make a call

Now we know when we're close to our friends, what are we likely to want to do when we're close? Drop in! But we're polite so we'll call them first. Let's change our list item click function to call the friend we've clicked. We can do this by firing a DIAL_ACTION intent.

    Intent i = new Intent();

    i.setAction(DIAL_ACTION);

    i.setData(new ContentURI(numbers.get(position)));

    startActivity(i);

The phone dialer has registered an IntentReceiver filtered on DIAL_ACTION so it will react to this.

Android is an environment where the biggest limitation is your imagination

And that's it.



I've got a list of a dozen or so changes to make it a little more useful and a half dozen ideas for projects that might actually make it into the running for some of that prize money. My conclusion? Android is everything a development kit should be – an environment where the biggest limitation is what you can imagine.

No comments:

Post a Comment