Skip to content
Nolan Lawson edited this page Apr 9, 2014 · 43 revisions

The following tutorial will get you started on your first PouchDroid Android app.

Table of Contents

Importing PouchDroid as a library project

If you've never worked with Android library projects before, the screenshots below will show you how to import PouchDroid from Git into your workspace, so you can use it as a library project.

If you already know how to do this: just clone https://github.com/nolanlawson/PouchDroid.git, add the library/ folder as a library project, then skip to Part 2.

In Eclipse/ADT, import a new project:

step1

Import from Git:

step2

Clone a URI:

step3

Type in https://github.com/nolanlawson/PouchDroid.git:

step4

You only need the master branch:

step5

Create a new Android application project. PouchDroid also comes with some examples and unit tests, which will appear in your workspace:

step7

Right-click your project and choose "Properties":

step8

Choose "Android," then click "Add..." to add a library project:

step9

Congratulations! PouchDroid is now a library project for your application:

step10

Using PouchDroid in your app

PouchDroid uses an invisible WebView as a bridge to talk to PouchDB through JavaScript. So you can only use PouchDroid in a running Activity or Fragment, not a Service.

androidtopouch

To use PouchDroid in an Activity, extend PouchDroidActivity:

public class MyActivity extends PouchDroidActivity {
  /* ... */
  public void onPouchDroidReady(PouchDroid pouchDroid) {
    // do stuff
  }
}

A PouchDroidListActivity is also available for ListActivities.

To use PouchDroid in a Fragment, extend PouchDroidFragment:

public class MyFragment extends PouchDroidFragment {
  /* ... */
  public void onPouchDroidReady(PouchDroid pouchDroid) {
    // do stuff
  }
}

The onPouchDroidReady() method is called in the Activity/Fragment lifecycle sometime after onResume(). PouchDroid automatically detaches itself from the Activity on the onPause() call, although you can still continue using it as long as the Activity is visible (so you don't need to check whether PouchDroid is closed or not).

If you cannot extend PouchDroidActivity or PouchDroidFragment (e.g. because you're using ActionBarSherlock or RoboGuice), then you can use the following idiom, which works exactly the same:

private PouchDroid pouchDroid;

private void onResume() {
  super.onResume();
  pouchDroid = new PouchDroid(this, new PouchDroid.OnReadyListener() {
      public void onReady(PouchDroid pouchDroid) {
        // do stuff
      }
  });
}

private void onPause() {
  super.onPause();
  if (pouchDroid != null) {
    pouchDroid.close();
  }
}

Working with POJOs

PouchDB and CouchDB are NoSQL document stores where the documents are encoded in JSON format. To convert between Java objects and JSON objects, PouchDroid uses the Jackson library.

Pouch/Couch documents require the reserved fields _id and _rev. To use these fields, your POJOs should extend PouchDocument, which provides the methods getPouchId(), setPouchId(), getPouchRev(), and setPouchRev().

For instance, consider a Student POJO:

public class Student extends PouchDocument {
  private String name;
  private boolean female;
  private List<String> grades;
  // getters and setters (must have a bare constructor)
}
Student student = new Student();
student.setName("Sally");
student.setFemale(true);
student.setGrades(Arrays.asList("A", "A", "B+", "A-", "B-", "A"));
student.setPouchId("someId");

This will be serialized to JSON as:

{
  "_id" : "someId",
  "_rev" : null,
  "name" : "Sally",
  "female" : true,
  "grades" : ["A", "A", "B+", "A-", "B-", "A"]
}

Your POJO can basically look however you want, as long as you follow these rules, which are good Java conventions anyway:

  1. Always have a bare, no-argument constructor. (Other constructors are OK.)
  2. Always have getters/setters for everything you want to serialize.

Of course, this is just Jackson, so you're free to get fancy with annotations, if you want. @JsonProperty (to rename variables) and @JsonIgnore are two that I like a lot.

@JsonProperty("batman")
public String getBruceWayne() {
  /* ... */
}

@JsonIgnore
public boolean isAlwaysParkingTheBatmobileAtWayneManor() {
  /* ... */
}

If your POJO already has an ID that you would likely to use as its Pouch ID, you can simply override getPouchId() and setPouchId():

public class Student extends PouchDocument {
  private int studentId;
  /* ... */
  public void setPouchId(String pouchId) {
    this.studentId = Integer.parseInt(pouchId);
  }
  public String getPouchId() {
    return Integer.toString(this.studentId);
  }
}

And if, for whatever reason, your POJOs cannot extend PouchDocument, then just have them implement PouchDocumentInterface, and you can implement all four methods: getPouchId(), setPouchId(), getPouchRev(), and setPouchRev().

Using the Java API

The PouchDroid API is a near-identical copy of the PouchDB API, which itself is a near-identical copy of the CouchDB API. Here is a diagram to illustrate:

xzibit

What this means for you, as a developer, is that you can basically just read the PouchDB docs to learn how to use PouchDroid. There are just a few changes to make it more Java-esque:

The constructor

A PouchDB object is typed based on the PouchDocuments that you want to put in. So if your POJO class is Student, you'll create a PouchDB<Student>:

PouchDB<Student> pouch = PouchDB.newPouchDB(Student.class, getPouchDroid(), "students.db");

Synchronous calls

PouchDB, like a lot of JavaScript libraries, is asynchronous. This is great when your language supports closures, but it just gets awkward in Java. So PouchDroid makes the PouchDB API synchronous, providing a more familiar experience for Java developers.

For instance, to get a Student document in PouchDB, you'd normally write JavaScript like this:

pouch.get('studentid', function(err, doc) {
  if (err) {
    // got an error!
  } else {
    // got a student!
  }
});

Whereas, in PouchDroid, you'd write Java like this:

try {
  Student student = pouch.get("studentid");
  // got a student!
} catch (PouchException e) {
  // got an error!
}

A PouchException is a runtime exception, so you can choose whether or not to catch it. Most of the time, you won't need to.

Every API call is translated in exactly this way: err objects become PouchException s, and whatever Pouch would normally return becomes a deserialized Java object. I also add some boolean arguments for commonly-used options (like continuous and include_docs), but you can always just include your options as a Map<String,Object> as well. It's pretty straightforward.

The only thing you need to know is that your calls to PouchDB should always be wrapped in an AsyncTask.doInBackground(), so that the UI thread doesn't block. This is just good practice for any database or filesystem operation in Android, since it avoids jank. Here's a good idiom:

final String studentId = "someId";

new AsyncTask<Void, Void, Student>() {

    @Override
    public Student doInBackground(Void input) {
      return pouchDB.get(studentId);
    }
  
    @Override
    public void onPostExecute(Student result) {
      // do something
    }

}.execute((Void)null);

If you're comfortable with callbacks, you can also use the asynchronous APIs by calling PouchDB.newAsyncPouchDB():

AsyncPouchDB<Student> asyncPouch = PouchDB.newAsyncPouchDB(Student.class, getPouchDroid(), "students.db");
asyncPouch.get("someid", new GetCallback<Student>() {
    @Override
    public void onCallback(PouchError err, Student info) {
      // do something            
    }
});

In fact, this is what the synchronous PouchDB uses under the hood! So you may (NB: probably won't) get some performance improvements if you go the async route.

Replication

Replication is a special case for synchronicity, because PouchDB doesn't necessarily inform us when replication has completed. Therefore, the synchronous replicateTo() and replicateFrom() methods return when the replication has started, not when it has finished.

A good rule of thumb is this: if you absolutely need to know when/if replication has finished, set continuous to false and use the asynchronous API:

asyncPouch.replicateTo("http://myserver:5984/students", false, new ReplicateCallback() {
            
    @Override
    public void onCallback(PouchError err, ReplicateInfo info) {
      if (err != null) {
        // replication complete!
      }          
    }
});

Otherwise, if you don't care when replication completes, set continuous to true and use the synchronous API:

PouchDB<Student> pouch = PouchDB.newPouchDB(Student.class, getPouchDroid(), "students.db");

// continuous bidirectional sync
pouch.replicateTo("http://myserver:5984/students", true);
pouch.replicateFrom("http://myserver:5984/students", true);