However, there is nothing that limits a ContentProvider to being implemented on top of a DB: any data source will do.
The OpenStreetMap Project is a collaborative mapping project, collecting map data all around the world for access under a liberal license (attribution/shared-alike). There are several ways of getting hold of OSM data, but one is the OSMXAPI interface. It allows getting data based on a bounding box and selection details. Returned from a query is (if successful) an XML document, which we parse with SAX.
Here, we implement a simple lookup only interface to OSMXAPI, which looks up public toilets.
Fundamentally, the query method, which is really the only we implement, goes like this:
- First of all we expect in the selection parameter something like "amenity=toilets". OSMXAPI is not a database, but this is as close as a DB selection parameter as we get in this context.
- Secondly in the selectionArgs we expect the four corner coordinates for which to check. This is a bit a rough-handling of this parameter, but the ContentProvider interface is a bit too tightly coupled to DBs to do this differently.
- Then we construct the URL for the OSMXAPI call and retrieve the XML document. If anything goes wrong here, we will just return an empty cursor.
- Now we parse the document with an XML parser, scanning for the nodes which contain the lat/long positions of our points. Every node is inserted into the data of a MatrixCursor, which is a generalised cursor essentially containing a table of data over which the user can iterate.
- We return this cursor to the user.
This is the full code for the ContentProvider:
What are the lessons learned from this?
package com.ucont;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.util.Log;
public class OSMPointsContentProvider extends ContentProvider {
private final String TAG = "OSMPointsContentProvider";
public static final String AUTHORITY = "org.osm.data";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/osmdata");
private final String osmxapiserver = "http://www.informationfreeway.org/api/0.5/";
private class OSMPointCursor extends MatrixCursor {
public OSMPointCursor(String[] columnNames) {
super(columnNames);
}
}
protected String constructQueryUrl(String selection, Double left, Double bottom, Double right, Double top) {
String query = String.format("node[%][bbox=%f,%f,%f,%f]", selection, left, bottom, right, top);
String url = osmxapiserver + query;
return url;
}
protected String constructQueryUrl(String selection, String top, String left, String bottom, String right) {
String query = String.format("node[%s][bbox=%s,%s,%s,%s]", selection, left, bottom, right, top);
String url = osmxapiserver + query;
return url;
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
return false;
}
private class OSMHandler extends DefaultHandler {
private OSMPointCursor cursor;
OSMHandler(OSMPointCursor cursor){
super();
this.cursor = cursor;
}
public void startElement (String uri, String name,
String qName, Attributes atts){
if (name.equals("node")){
Object[] columnValues = {atts.getValue("lat"), atts.getValue("lon")};
cursor.addRow(columnValues);
}
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
String[] columnNames = {"lat", "lon" };
OSMPointCursor result = new OSMPointCursor(columnNames);
URL url = null;
if (null == selection){
selection = "*=*";
}
try {
url = new URL (constructQueryUrl(selection, selectionArgs[0], selectionArgs[1], selectionArgs[2], selectionArgs[3]));
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xmlReader = sp.getXMLReader();
OSMHandler handler = new OSMHandler(result);
xmlReader.setContentHandler(handler);
xmlReader.setErrorHandler(handler);
xmlReader.parse(new InputSource(url.openStream()));
} catch (MalformedURLException e){
Log.e(TAG, e.getMessage());
} catch (IOException e){
Log.e(TAG, "IOException " + e.getMessage());
} catch (SAXException e){
Log.e(TAG, "SAX " + e.getMessage());
} catch (ParserConfigurationException e) {
Log.e(TAG, "PC Exception " + e.getMessage());
}
return result;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
}
- The ContentProvider interface is a bit too closely aligned with databases. A more general concept, based on key/values to match would have been much better. Android developers have somehow recognised this and provided the MatrixCursor for a generalised return value.
- It would be nice if a ContentProvider could be queried which operations it supported. That way there would be a general way of implementing a read-only or write-only ContentProvider. The interface for insert does not allow a ContentProvider to throw an exception to indicate that inserts are not supported.
- It would be good to have things like cost functions on ContentProviders (e.g. the cheapest way of providing a certain content or the most up-to-date). In that sense ContentProvider is some two decades behind what has been done in terms of service provision negotiation in distributed systems.
1 comment:
Thanks a million for this!
I never thought I would come around an example of a content provider that does NOT use sqlite! I have been searching for ages. Now I finally have a starting point.
Post a Comment