-
Notifications
You must be signed in to change notification settings - Fork 7
Getting Started
The following tutorial will get you started on your first PouchDroid Android app.
- Part 1: Importing PouchDroid as a library project
- Part 2: Using PouchDroid in your app
- Part 3: Working with POJOs
- Part 4: Using the Java API
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:
Import from Git:
Clone a URI:
Type in https://github.com/nolanlawson/PouchDroid.git
:
You only need the master
branch:
Create a new Android application project. PouchDroid also comes with some examples and unit tests, which will appear in your workspace:
Right-click your project and choose "Properties":
Choose "Android," then click "Add..." to add a library project:
Congratulations! PouchDroid is now a library project for your application:
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.
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();
}
}
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:
- Always have a bare, no-argument constructor. (Other constructors are OK.)
- 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()
.
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:
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:
A PouchDB
object is typed based on the PouchDocument
s 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");
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 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);