/hiccup

HTTP semantics and REST for Android ContentProviders.

Primary LanguageJavaMIT LicenseMIT

Overview

Hiccup is a RESTful approach to ContentProviders using HTTP semantics.

The idea is to encourage designing ContentProviders like web services. They are so similar that we can benefit from some established best practices, including RESTful interfaces and MVC-like concepts. It does not make actual HTTP requests to the internet.

Some benefits to this approach include allowing any persistence implementation in ContentProviders (eg, sqlite, shared prefs, ORMs) and permitting versioned endpoints/resources for cross-process data sharing.

Status

Build Status

Approaching a 1.0 pre-release. Grab 1.0 snapshot version like so:

<dependencies>
  <dependency>
    <groupId>com.amplify</groupId>
    <artifactId>hiccup</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>
</dependencies>

Currently supports: GET, POST, PUT, DELETE, and batch requests

Goals

  1. improve code maintainability of ContentProviders
  2. separate client/provider concerns
  3. allow UI/data layers to evolve independently
  4. expose a clean & simple api for developers
  5. be super lightweight
  6. remain compatible within normal ContentProvider behavior

Motivation

Android's ContentProvider is a great tool for data access and cross-app sharing, but its interface is confusing and has several limitations. Mainly, its interface is half REST and half SQL, which result in the following:

  1. clients (eg, Activities) are forced to know underlying db schema via sql projections, where clauses, etc.
  2. restructuring tables (data normalization) breaks client code
  3. db transactions, joins, cascades, etc. are difficult to support when using uri's + sql
  4. data integrity is difficult to maintain when it's left up to clients
  5. assumes sqlite backend (ie, does not easily support in-memory, file, shared pref, NoSQL, etc.)
  6. data/resources cannot be versioned

Usage

Client (eg, Activity) makes GET/POST/PUT/DELETE requests

@Override
public void onCreate() {
    super.onCreate();
    setContentView(R.layout.whatever);

    findViewById(R.id.delete_button).setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                Uri uri = Uri.parse("content://com.your.authority/posts/" + postId);
                hiccupClient.delete(uri); // on UI thread just for demo purposes
            }
    });
}

ContentProvider dispatches requests to controllers

private HiccupService hiccupService;

@Override
public boolean onCreate() {
    super.onCreate();
    SQLiteOpenHelper dbHelper = createDbHelper();
    hiccupService = new HiccupService("com.your.authority")
            .newRoute("posts/#", new PostsController(dbHelper))
            .newRoute("posts/#/comments", new CommentsController(dbHelper))
            // etc
            ;
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
    // Delegate to Controller based on incoming uri and registered #newRoute()'s
    // (intentionally ignores the selection+args)
    return hiccupService.delegateDelete(uri);
}

Controller handles transactions, data integrity, etc.

public class PostsController implements Controller {
    // constructor, etc...

    @Override
    public int delete(Uri uri) {
        String postId = uri.getPathSegments().get(1);
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        db.beginTransaction();
        try {
            db.delete("comments", "post_id = ?", new String[]{postId});
            db.delete("posts", "_id = ?", new String[]{postId});
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
        Uri changedUri = Uri.parse("content://com.your.authority/posts");
        contentResolver.notifyChange(changedUri, null);
    }
}

Thanks!

To Dave Cameron, a former colleague, who inspired some ideas here. Please be sure to check oot his profile, eh?