In case you hadn’t heard, Google has released a preview of AppEngine, it’s cloud/platform service. It provides a full application development and deployment stack that has access to google services like email, login, and datastore. It also provides a python-based application development environment called webapp as well as supporting other python web frameworks, like django.

Since location-based services for mobile phones are all the rage, I thought I would walk through how to create a simple location sharing web service on AppEngine and interact with it in Android. I will show you how to create a simple REST-ish API that consumes HTTP requests and outputs simple XML data. It probably won’t be exciting for people interested in shiny web applications, but using this approach provides a flexible protocol for interacting with remote service from a mobile phone, allowing the data to be consumed and displayed in a native phone application.

After that, I will show you how to use Android’s LBS and networking APIs to interact with the service.

Setup
Create a new AppEngine project and create a file called location.py. First, we need to configure the app.yml with the url mapping for our service. For this example I will simply use “/endpoint” as my mapping to the location.py handler.


application: location
version: 1
runtime: python
api_version: 1

handlers:
- url: /endpoint
  script: location.py

There are many more ways to define your url mapping to provide RESTful urls for your app, see here for more info.

location.py
The source for the service can be found here. I am going to step through the file from the top down.

Model
We want to create a location domain model that can be used with the datastore api. To do this we define a Location class that inherits from db.Model. We will create fields for latitude, longitude, and date.


class Location(db.Model):
  lat = db.IntegerProperty()
  lon = db.IntegerProperty()
  date = db.DateTimeProperty(auto_now_add=True)

lat and lon are stored as Integers because it makes things a little easier to use with some of the Android Map and Location APIs.

Since we want to be able to output our model as XML we make a helper method that makes a one line XML element with lat and lon as attributes:


def printloc(self,location):
      return '<location lat=\'%(lat)s\' lon=\'%(lon)s\'></location>\n' % {'lat':location.lat, 'lon':location.lon} 

Models in AppEngine/webapp have a to_xml() method that allows them to be exported to XML automatically, the format is much more verbose than I would like to deal with in this tutorial, but they do provide an interesting look as to how the data is stored and read in the datastore/BigTable.

Methods
The next two methods get called when a request is made. They are mapped to the GET and POST http methods, this makes it easy to design RESTful interfaces for our web service. In our example we want a POST to create a new location, while GET will fetch a list of the 10 most recent locations.

Post


  def post(self):
      location = Location()
      location.lat = int(self.request.get('lat'))
      location.lon = int(self.request.get('lon'))
      location.put()
      self.response.out.write(self.printloc(location))

This creates a new location object and stores the two parameter values in it and saves the location to the data store. We then write the location out to the response.

The output of this method will look something like this:


<location lat="37422375" lon="-122096532"/>

Get


def get(self):
    locations = db.GqlQuery("SELECT * FROM Location ORDER BY date DESC LIMIT 10")
    self.response.headers['Content-Type'] = 'text/xml'
    self.response.out.write('<list>')
    for l in locations:
        self.response.out.write(self.printloc(l))
    self.response.out.write('</list>')

This code fetches the latest 10 locations using a GQL query and wraps them in a element.

The output of this call will look something like this:


<list>
<location lat="37422375" lon="-122096532"/>
<location lat="37429756" lon="-122100756"/>
<location lat="37428218" lon="-122101459"/>
<location lat="37422383" lon="-122096532"/>
<location lat="37422378" lon="-122096531"/>
<location lat="37422384" lon="-122096533"/>
<location lat="37449370" lon="-122119525"/>
<location lat="37449355" lon="-122119523"/>
<location lat="37454563" lon="-122130219"/>
<location lat="37454443" lon="-122130012"/>
</list>

main


def main():
  application = webapp.WSGIApplication(
                                       [('/endpoint', LocationService)],
                                       debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
  main()

This code binds our handler to the /endpoint address, and runs the service.

Android integration
I will now briefly demonstrate how to interact with the service from within Android. If you are like me, and were a little late trying to sign up for the AppEngine preview program, you are running your web service locally. You need to run the development server with the “-a ADDRES” where ADDRESS is your internal network address. Android has an internal network loopback that loops any requests made from the phone to “localhost” back to the phone so you won’t be able to access your service if it is running on localhost.

Our first step is to build a new MapActivity that contains a MapView and two menu actions “Update Location” and “Fetch Locations”. The source for this activity can be found here.

Update Location:
The definition of our Post Current Location menu item which is defined inside of public boolean onCreateOptionsMenu(Menu menu):


menu.add(0, 1, "Post Current Location", new Runnable(){

			public void run() {

				// get current location and use it as params to our API call
				LocationManager locMan = (LocationManager) MiniLocationMap.this
						.getSystemService(Context.LOCATION_SERVICE);
				Location loc = locMan.getCurrentLocation("gps");

				 Map headers = new HashMap();

		        String text= "lat=" +(int)(loc.getLatitude()*1E6) + "&lon=" + (int)(loc.getLongitude()*1E6;
		        byte[] bytes = text.getBytes();
		        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
				mRequestQueue.queueRequest(
		                "http://10.0.0.199:8080/endpoint",
		                "POST", headers, new LocationEventHandler(mHandler), bais, bytes.length, false);
			}

		});

This gets your current location in the same way that we did in our fire eagle example. It then constructs a POST request with the latitude and longitude as parameters for our web service. You need to convert the latitude and longitude to integers, there is probably a simpler way to do it than above. This example uses the RequestQueue class which provides an interface for making http requests. For more information on performing these requests you should definitely check out this blog, which is full of great android examples.

The LocationEventHandler class handles the data returned from the request. In my example the EventHandler uses the XML pull parser to parse the XML data into a DemoLocation object which is then passed back to the Map Activity using it’s handler. In the Handler the location is extracted from the Message object and used to center the map on the location just posted:


public void handleMessage(Message msg) {
			// TODO Auto-generated method stub
			super.handleMessage(msg);

			if(msg.what == 123){
				DemoLocation currentLocation = (DemoLocation) msg.getData().get("location");
				mMapController.centerMapTo(new Point(currentLocation.lat, currentLocation.lon), true);

			}
				}
		}

Retrieving Locations


menu.add(0, 0, "Retrieve Locations", new Runnable(){

			public void run() {
				 Map headers = new HashMap();
				mRequestQueue.queueRequest(
		                "http://10.0.0.199:8080/endpoint",
		                "GET", headers, new LocationListHandler(mHandler), null, 0, false);
			}

		});

This creates a GET http request and uses LocationListHandler to create an ArrayList of locations that can be used by our map. To keep the tutorial short I won’t actually do anything with the list of locations, but they could be used with an overlay to draw points on the map of all the places you have recently been.


This fairly primitive implementation should be enough to get you started creating web services in AppEngine and using them in android. Security is obviously an issue I ignored. The built-in google user account api is built around user authentication using web forms and http redirects, similar to OpenID. This makes it a little difficult to create APIs using just google accounts. Implementing OAuth and having the web-based authorizorization part of the process use the google user api, then having a seperate table storing a reference to the user’s account, applications, and access tokens would be one approach to solving this problem.

Overall, AppEngine was easy to use and provides a nice set of functionality to build on top of. Having your web stack and client stack all under the google umbrella is kind of interesting. The barrier of entry to developing and deploying a web app is now so low that I can see people building their own personal web services for things like their desktop and mobile phones. You now control your personal information and can use open standards for sharing that information.

Posted on April 9th, 2008 | filed under android, mobile | Trackback |

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>