Introduction

Obsolete documentation

For the latest Couchbase Mobile documentation, visit the Couchbase Mobile developer portal.

This guide provides information for developers who want to use Couchbase Lite to develop apps for Android devices. To learn about Couchbase Lite, read the Couchbase Lite Concepts Guide.

This guide assumes you are familiar with developing software for Android devices. If you are new to Android development, take a look at the training classes for Android developers available on the Android website before beginning to work with Couchbase Lite.

Tutorial

Note

This tutorial is compatible with the Couchbase Lite Android beta 2 release. If you built Couchbase Lite Android from post-beta 2 source code, refer to the ReadMe file in the GitHub repository for up-to-date information about building an app with Couchbase Lite.

This section contains a Hello World tutorial that shows how to set up and build an Android app with Couchbase Lite. The tutorial has separate sections written for specific Android IDEs:

If you want to play with a demonstration app, you can download and run GrocerySync from our GitHub repository.

Building your first app with Android Studio

This section describes how to build a Couchbase Lite app by using Android Studio.

Setting up the Android Studio development environment

Before you can build an app, you need to set up your development environment:

  1. Download and install Android Studio.

  2. Launch Android Studio.

  3. From the Quick Start menu on the welcome screen, select Configure > SDK Manager.

    If you already have a project open, you can open the SDK Manager by selecting Tools > Android > SDK Manager from the Android Studio menu bar.

  4. In Android SDK Manager, select the following items and then click Install packages:

    • Tools/Android SDK Tools
    • Tools/Android SDK Platform-tools
    • Tools/Android SDK Build-tools
    • Android API (currently recommended: API 17)
    • Extras/Google Repository
    • Extras/Android Support Repository

Creating an app with Android Studio

This section shows how to create a simple Hello World app for an Android device with Couchbase Lite. It uses Maven to add the Couchbase Lite dependencies.

Step 1: Create a new project

  1. Launch Android Studio.

  2. In the Welcome to Android Studio screen, choose New Project.

  3. In the New Project window, enter the application name, module name, package name, and project location.

    This example uses HelloWorld for the application name.

  4. Set the minimum required SDK to API 9: Android 2.3 (Gingerbread) or later and use the currently recommended Android API.

    After you fill in the fields, the New Project window should look something like this:

  5. Click Next, and then move through the remaining setup screens and enter settings as necessary (or just accept the defaults).

  6. Click Finish.

Step 2: Add Couchbase Lite dependencies via Maven

  1. Expand the HelloWorld folder, and then open the build.gradle file.

    You should see a file that looks something like this:

    If the build.gradle file is empty, then you are looking at the wrong one. Make sure you open the one in the HelloWorld folder (and not the one at the project level).

  2. In the build.gradle file, add the following lines to the top-level repositories section (not the one under the buildscript section) so it can resolve dependencies through Maven Central and the Couchbase Maven repository:

     maven {
         url "http://files.couchbase.com/maven2/"
     }
     mavenLocal()
    

    After you add the extra lines, the repositories section should look like this:

     repositories {
         mavenCentral()
         maven {
             url "http://files.couchbase.com/maven2/"
         }
         mavenLocal()
     }
    
  3. Select Tools > Open Terminal, create a libs directory, and then change to the new directory:

     $ mkdir libs
     $ cd libs
    
  4. In the Terminal window, download td_collator_so.jar into the libs directory.

    You can use wget or curl to download the file:

     $ wget http://cl.ly/Pr1r/td_collator_so.jar
     or
     $ curl -OL http://cl.ly/Pr1r/td_collator_so.jar
    
  5. In the build.gradle file, add the following lines to the top-level dependencies section (not the one under the buildscript section).

     // hack to add .so objects
     compile fileTree(dir: 'libs', include: 'td_collator_so.jar')  
     compile 'com.couchbase.cblite:CBLite:1.0.0-beta2'
    

    After you add the extra lines, the dependencies section should look similar to this:

     dependencies {
         compile 'com.android.support:appcompat-v7:+'
         // hack to add .so objects
         compile fileTree(dir: 'libs', include: 'td_collator_so.jar')
         compile 'com.couchbase.cblite:CBLite:1.0.0-beta2'
     }
    
  6. In the Android Studio tool bar, click Sync Project with Gradle Files.

  7. In the Android Studio tool bar, click Run.

    When requested, start the emulator. You should see the app start in the emulator and the text “Hello World” in the app window, similar to the following figure:

Troubleshooting tips

Running the empty app at this point verifies whether the dependencies are set up correctly. If the app doesn’t run properly for you, try the following troubleshooting tips:

  • Errors in the build.gradle file are a common cause of problems:
    • Double-check the spelling of all entries in the file.
    • Make sure all code added to the file is located in the correct sections.
    • Verify the path for Couchbase Lite in the compile statement in the dependencies section.
    • Compare your file to this sample build.gradle file.
  • Couchbase Lite for Android does not currently build correctly with Proguard. If you get build errors that mention Proguard, you can disable it by changing the **build.gradle** file `runProguard` setting in the **android** section to false. When you change it, the *android* section should look something like the following code:

android {
     buildTypes {
        release {
            runProguard false
            proguardFile ...
        }
    }
 }

Step 3: Add the HelloWorld code

  1. Open the MainActivity.java file.
  2. Add the following lines of code to the imports section at the top of the file:

     import com.couchbase.lite.*;
     import com.couchbase.lite.util.Log;
    
     import java.io.IOException;
     import java.text.SimpleDateFormat;
     import java.util.Calendar;
     import java.util.GregorianCalendar;
     import java.util.HashMap;
     import java.util.Map;
    
  3. Add the following code at the end of the onCreate method in the MainActivity.java file, which is located in the /HelloWorld/HelloWorld/src/main/java/com/couchbase/helloworld directory.

    final String TAG = "HelloWorld";
     Log.d(TAG, "Begin Hello World App");
    
    

    // create a manager Manager manager = null; try { manager = new Manager(getApplicationContext().getFilesDir(), Manager.DEFAULT_OPTIONS); } catch (IOException e) { Log.e(TAG, "Cannot create manager object"); return; }

    // create a name for the database and make sure the name is legal String dbname = "hello"; if (!Manager.isValidDatabaseName(dbname)) { Log.e(TAG, "Bad database name"); return; }

    // create a new database Database database = null; try { database = manager.getDatabase(dbname); } catch (CouchbaseLiteException e) { Log.e(TAG, "Cannot get database"); return; }

    // get the current date and time SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd’T'HH:mm:ss.SSS'Z'"); Calendar calendar = GregorianCalendar.getInstance(); String currentTimeString = dateFormatter.format(calendar.getTime());

    // create an object that contains data for a document Map<String, Object> docContent = new HashMap<String, Object>(); docContent.put("message", "Hello Couchbase Lite"); docContent.put("creationDate", currentTimeString);

    // display the data for the new document Log.d(TAG, "docContent=" + String.valueOf(docContent));

    // create an empty document Document document = database.createDocument();

    // write the document to the database try { document.putProperties(docContent); } catch (CouchbaseLiteException e) { Log.e(TAG, "Cannot write document to database", e); }

    // save the ID of the new document String docID = document.getId();

    // retrieve the document from the database Document retrievedDocument = database.getDocument(docID);

    // display the retrieved document Log.d(TAG, "retrievedDocument=" + String.valueOf(retrievedDocument.getProperties()));

    Log.d(TAG, "End Hello World App");

The code you just added creates a new database, and then creates a document, stores the document in the database, and retrieves the document. This section contains additional notes that supplement the comments in the code.

It creates a shared Manager object that manages a collection of databases. The Manager object can be used only in a single thread.

After it creates a name for the new database, it validates the name. A database name can consist of only lowercase alphabetic characters (a-z), digits (0-9) and a few special characters (_$()+-/), so it’s important to validate the name.

To create the database, it calls getDatabase(), which is a method in the Manager class that returns a Database object. If the database does not already exist, getDatabase() creates it.

Map objects provide JSON-compatible representations of data that are suitable for creating documents that you can store in the database. The document created by the code is a HashMap<String, Object> object named docContent that contains only two keys, message and creationDate. message contains the string “Hello Couchbase Lite!”, and creationDate contains the time and date the document was created. The document content is written out to the log to show its content.

An empty Document object named document is created. The document content is added to the empty document and it is saved to the database by using the Document class putProperties() method. If the document cannot be written to the database, an exception is thrown.

When the document is saved to the database, Couchbase Lite generates a document identifier property named _id and a revision identifier property named _rev, and adds them to the document. The generated _id for the new document is available via the getId() method of the Document class.

The saved document is retrieved from the database by using the Database class getDocument() method. The retrieved document is written out to the log to show its content, which now includes the _id and _rev properties created by Couchbase Lite.

Step 4: Build and run HelloWorld

  1. Click Run.

  2. View the Hello World app messages in the logcat.

    If you filter the logcat output on the string /Hello, you can see just the messages from Hello World:

Building your first app with Eclipse

This section describes how to build a Couchbase Lite app by using Eclipse.

Setting up the Eclipse development environment

Before you can build an app, you need to set up your development environment. To get set up with Eclipse, you can use one of the following methods:

  • (Recommended method). Download and install the Android Developer Tools (ADT) Bundle, which includes the essential Android SDK components and a version of the Eclipse IDE with built-in ADT. This tutorial was developed with the ADT Bundle.

  • If you already have Eclipse installed and prefer to use that version, you can install the Android SDK by following the instructions for using an existing IDE found on the ADT Bundle page.

Creating an Android app with Eclipse

This section shows how to create a simple Hello World app for an Android device with Couchbase Lite.

Step 1: Create a new project

  1. Launch Eclipse.

  2. Select File > New > Android Application Project.

  3. In the New Project window, enter the application name, module name, package name, and project location.

    This example uses HelloWorldEclipse for the application name.

  4. Set the minimum required SDK to API 9: Android 2.3 (Gingerbread) or later and use the currently recommended Android API.

    After you fill in the fields, the New Project window should look something like this:

  5. Click Next, and then move through the remaining setup screens and enter settings as necessary (or just accept the defaults).

  6. When you get to the last setup screen, click Finish.

  7. Open the AndroidManifest.xml file (it’s located at the root level of your project directory), and add the following line to the file as a child of the <manifest> element.

     <uses-permission android:name="android.permission.INTERNET" />
    
  8. Save the AndroidManifest.xml file.

Step 2: Add Couchbase Lite

  1. Download the latest version of Couchbase Lite from http://www.couchbase.com/download#cb-mobile.

  2. Decompress the zip file.

  3. Copy all the files into the libs folder in the HelloWorldEclipse project.

  4. Download http://cl.ly/Pr1r/td_collator_so.jar.

  5. Rename the downloaded td_collator_so.jar file to td_collator_so.zip.

  6. Decompress the td_collator_so.zip file.

    The zip file decompresses into a lib directory that contains several folders:

  7. Copy all of the files into the libs folder in the HellowWorldEclipse project.

    After the Couchbase Lite and td_collator_so files are copied into the libs directory, it should look similar to the following figure:

  8. Click Run and verify the app runs properly.

    When requested, start the emulator. You should see the app start in the emulator and the text “Hello World” in the app window, similar to the following figure:

    Running the empty app at this point verifies whether the dependencies are set up correctly. The app won’t run properly if you have Android Studio running simultaneously with the ADT bundle Eclipse.

Step 3: Add the HelloWorld code

  1. Open the MainActivity.java file.
  2. Add the following lines of code to the imports section at the top of the file:

     import com.couchbase.lite.*;
     import com.couchbase.lite.util.Log;
    
     import java.io.IOException;
     import java.text.SimpleDateFormat;
     import java.util.Calendar;
     import java.util.GregorianCalendar;
     import java.util.HashMap;
     import java.util.Map;
    
  3. Add the following code at the end of the onCreate method in the MainActivity.java file, which is located in the /HelloWorld/HelloWorld/src/main/java/com/couchbase/helloworld directory.

    final String TAG = "HelloWorld";
     Log.d(TAG, "Begin Hello World App");
    
    

    // create a manager Manager manager = null; try { manager = new Manager(getApplicationContext().getFilesDir(), Manager.DEFAULT_OPTIONS); } catch (IOException e) { Log.e(TAG, "Cannot create manager object"); return; }

    // create a name for the database and make sure the name is legal String dbname = "hello"; if (!Manager.isValidDatabaseName(dbname)) { Log.e(TAG, "Bad database name"); return; }

    // create a new database Database database = null; try { database = manager.getDatabase(dbname); } catch (CouchbaseLiteException e) { Log.e(TAG, "Cannot get database"); return; }

    // get the current date and time SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd’T'HH:mm:ss.SSS'Z'"); Calendar calendar = GregorianCalendar.getInstance(); String currentTimeString = dateFormatter.format(calendar.getTime());

    // create an object that contains data for a document Map<String, Object> docContent = new HashMap<String, Object>(); docContent.put("message", "Hello Couchbase Lite"); docContent.put("creationDate", currentTimeString);

    // display the data for the new document Log.d(TAG, "docContent=" + String.valueOf(docContent));

    // create an empty document Document document = database.createDocument();

    // write the document to the database try { document.putProperties(docContent); } catch (CouchbaseLiteException e) { Log.e(TAG, "Cannot write document to database", e); }

    // save the ID of the new document String docID = document.getId();

    // retrieve the document from the database Document retrievedDocument = database.getDocument(docID);

    // display the retrieved document Log.d(TAG, "retrievedDocument=" + String.valueOf(retrievedDocument.getProperties()));

    Log.d(TAG, "End Hello World App");

The code you just added creates a new database, and then creates a document, stores the document in the database, and retrieves the document. This section contains additional notes that supplement the comments in the code.

It creates a shared Manager object that manages a collection of databases. The Manager object can be used only in a single thread.

After it creates a name for the new database, it validates the name. A database name can consist of only lowercase alphabetic characters (a-z), digits (0-9) and a few special characters (_$()+-/), so it’s important to validate the name.

To create the database, it calls getDatabase(), which is a method in the Manager class that returns a Database object. If the database does not already exist, getDatabase() creates it.

Map objects provide JSON-compatible representations of data that are suitable for creating documents that you can store in the database. The document created by the code is a HashMap<String, Object> object named docContent that contains only two keys, message and creationDate. message contains the string “Hello Couchbase Lite!”, and creationDate contains the time and date the document was created. The document content is written out to the log to show its content.

An empty Document object named document is created. The document content is added to the empty document and it is saved to the database by using the Document class putProperties() method. If the document cannot be written to the database, an exception is thrown.

When the document is saved to the database, Couchbase Lite generates a document identifier property named _id and a revision identifier property named _rev, and adds them to the document. The generated _id for the new document is available via the getId() method of the Document class.

The saved document is retrieved from the database by using the Database class getDocument() method. The retrieved document is written out to the log to show its content, which now includes the _id and _rev properties created by Couchbase Lite.

Step 4: Build and run HelloWorld

  1. Click Run.

  2. View the Hello World app messages in the logcat.

    If you filter the logcat output on the string tag:/HelloWorld, you can see just the messages from Hello World:

Adding Couchbase Lite to Your Project

You can add Couchbase Lite to your Android project by using one of the following methods:

Adding a Maven Dependency

Follow these steps to add the Maven dependency to your project:

  1. Add the following repositories section to the build.gradle file so it can resolve dependencies through Maven Central and the Couchbase Maven repository:

     repositories {
         mavenCentral()
         maven {
             url "http://files.couchbase.com/maven2/"
         }
         mavenLocal()
     }
    
  2. If there is no libs directory in the MyProject directory, open a Terminal window, create a libs directory, and then change to the new directory. For example:

     $ cd ~/AndroidStudioProjects/MyProjectProject/MyProject
     $ mkdir libs
     $ cd libs
    
  3. Download td_collator_so.jar into the libs directory.

    You can use wget or curl to download the file:

     $ wget http://cl.ly/Pr1r/td_collator_so.jar
     or
     $ curl -OL http://cl.ly/Pr1r/td_collator_so.jar
    
  4. In the build.gradle file, add the following lines to the top-level dependencies section (not the one under the buildscript section).

     dependencies {
        // ...
        // hack to add .so objects
        compile fileTree(dir: 'libs', include: 'td_collator_so.jar')  
        compile 'com.couchbase.cblite:CBLite:1.0.0-beta2'
     }
    
  5. Make sure that your dependency on the Android Support library looks like this:

     compile 'com.android.support:support-v4:13.0.+'
    

    You can also use com.android.support:support-v4:18.0.0.

Adding a JAR File Dependency

Follow these steps to add the JAR file to your project:

  1. Download the latest release of Couchbase Lite for Android from http://www.couchbase.com/download#cb-mobile.

  2. Extract the .zip file to the libs directory of your project.

  3. Modify the build.gradle file to include all jars in the libs directory:

     dependencies {
        ...
        compile fileTree(dir: 'libs', include: '*.jar')
     }
    

Adding Source Files to Your Project

If you need to debug Couchbase Lite, you can include the Couchbase Lite code in your project rather than using a JAR file or the Maven artifact dependencies. If you choose to add the source files, make sure you remove any Maven or JAR file dependencies that you used previously.

Follow these steps to add Couchbase Lite source files directly to your project:

Add submodules

  1. Change to the parent MyProject directory, which contains the settings.gradle file.

     $ cd MyProject 
    
  2. Add the required submodule:

     $ git submodule add https://github.com/couchbase/couchbase-lite-android-core.git CBLite
    
  3. Add any optional submodules that you need:

     $ git submodule add https://github.com/couchbase/couchbase-lite-android-listener.git CBLiteListener
     $ git submodule add https://github.com/couchbase/couchbase-lite-android-javascript.git CBLiteJavascript
    

Update Gradle files

  1. Add the following line to the settings.gradle file:

     include ':MyProject', ':CBLite'
    
  2. If you included any optional submodules, add include statements for them to the settings.gradle file.

  3. Add the following dependency to the MyProject/MyProject/build.gradle file:

     dependencies {
         ... existing dependencies ...
         compile project(':CBLite')
    

    }

  4. If you included any optional submodules, add compile statements for them to the MyProject/MyProject/build.gradle file.

Developing Apps

This section describes how to use Couchbase Lite in your Android app. Many of the examples in this section are from the Grocery Sync Demo app. You can download it from GitHub to view the examples in context of a complete app.

Working with databases

Databases are represented by the Database class and managed by the Manager class.

When your app is launched for the first time, you need to set up a database. Depending on your app design, you might need to set up an initial database the first time a user launches your app and then connect to the existing database upon subsequent launches. Each time the app launches, you must check whether the database exists in Couchbase Lite.

Database names must begin with a lowercase letter. You can test a database name for validity by calling isValidDatabaseName(). The following characters are valid in database names:

  • lowercase letters: a-z
  • numbers: 0-9
  • special characters: _$()+-/

Creating a database

To create a database in your app, you need to create a Database instance by using the getDatabase() method provided in the Manager class. If the database specified in a call to getDatabase() already exists, the existing database is opened. If the database does not exist, a new database is created. In the following example, manager is declared as static because it needs to be shared with other parts of the app.

protected static Manager manager;
private Database database;
public static final String DATABASE_NAME = "grocery-sync";

// create a manager
manager = new Manager(getApplicationContext().getFilesDir(), Manager.DEFAULT_OPTIONS);

// create a new database
database = manager.getDatabase(DATABASE_NAME);

Connecting to an existing database

If the database already exists, you can open it by using the getExistingDatabase() method:

database = manager.getExistingDatabase(DATABASE_NAME);

If the specified database does not exist, the getExistingDatabase() method throws an exception.

Working with documents

Documents are represented by the Document class. You create and retrieve documents by using methods in the Database class, and you work with the document content by using methods in the Document class.

Creating documents

You create a new document when the user creates a persistent data item in your app, such as a reminder, a photograph or a high score. To create a new document, construct a JSON-compatible representation of the data, instantiate a new document and save the data to the new document.

In the following example, properties is a HashMap object that provides a JSON-compatible representation of the document data.

// set up a time stamp to use later
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Calendar calendar = GregorianCalendar.getInstance();
String currentTimeString = dateFormatter.format(calendar.getTime());

// create an object to hold document data
Map<String, Object> properties = new HashMap<String, Object>();

// store document data 
properties.put("text", text);
properties.put("check", Boolean.FALSE);
properties.put("created_at", currentTimeString);

// create a new document
Document document = database.createDocument();

// store the data in the document
document.putProperties(properties);

When you create a document by calling the createDocument: method, Couchbase Lite generates a random unique identifier (a long string of hex digits) for it. You can choose your own identifier by calling getDocument() instead. When creating your own identifiers, remember that each identifier must be unique so you don’t get conflict errors when you save documents.

Reading documents

To retrieve the contents of a document, you need to obtain the Document object representing it by requesting the document from the Database object. After you obtain the document, you can get the contents from that object.

You can retrieve a Document object in the following ways:

  • If you know the document’s ID, you can call getDocument() or getExistingDocument() on the Database object. These methods are similar but operate differently depending on whether the document exists. If the document exists, each method loads the document from the database. If the document does not exist, getDocument() creates a new document with the given ID and getExistingDocument() returns null.

  • If you are iterating the results of a view query or createAllDocumentsQuery() call, you can call getDocument() on the QueryRow object.

Here’s an example that shows how to retrieve a document by requesting it from a QueryRow object:

QueryRow row = (QueryRow) adapterView.getItemAtPosition(position);
Document document = row.getDocument();

After you retrieve the document, you can get its content by calling getProperties() on it:

Map<String, Object> curProperties = document.getProperties();

You can also retrieve individual properties from the document by calling getProperty() on it:

boolean checked = ((Boolean)document.getProperty("check")).booleanValue();

Updating documents

To update a document after you modify its attributes, call putProperties: again. Couchbase Lite uses Multiversion Concurrency Control (MVCC) to manage changes to documents. When you update a document, you must tell Couchbase Lite which revision you updated so it can stop you if there were any updates to the document in the meantime. If it didn’t, you would wipe out those updates by overwriting them.

Documents contain a special property named _rev whose value is the current revision ID. The revision ID is a long, hex string. When you update a document, the new properties dictionary must contain a _rev key whose value is the ID of the revision that you’re updating.

The _rev property is already in the dictionary you got from the Document, so all you need to do is modify the properties dictionary and hand back the modified dictionary that still contains the _rev property to putProperties:.

The following example retrieves a document, gets a property named check from the document, toggles the value of check, and then writes an updated revision of the document to the database.

// get a document from a QueryRow object
Document document = row.getDocument();

// get the current document properties
Map<String, Object> curProperties = document.getProperties();

// make a copy of the document properties
Map<String, Object> newProperties = new HashMap<String, Object>();
newProperties.putAll(curProperties);

// get the current value of the check property
boolean checked = ((Boolean) newProperties.get("check")).booleanValue();

// toggle check value and store in copy of properties
newProperties.put("check", !checked);

// update the document with the new property values
try {
    document.putProperties(newProperties);
    itemListViewAdapter.notifyDataSetChanged();
} catch (Exception e) {
    Toast.makeText(getApplicationContext(), "Error updating database, see logs for details", Toast.LENGTH_LONG).show();
    Log.e(TAG, "Error updating database", e);
}

Handling update conflicts

Due to the realities of concurrent programming, the previous example code is vulnerable to a race condition. If something else updates the document in between the calls to the getProperties() and putProperties() methods, the operation fails.

Even if your app is single-threaded, most Couchbase Lite apps use replication, which runs in the background. So it’s possible that one of your users might get unlucky and find that Couchbase Lite received a remote update to that very document, and inserted it a moment before he tried to save his own update. He’ll get an error about a conflict. Then he’ll try the operation again, and this time it’ll work because by now your Document has updated itself to the latest revision.

This is, admittedly, unlikely to happen in the above example because the elapsed time between getting and putting the properties is so short (microseconds, probably). It’s more likely in a situation where it takes the user a while to make a change. For example, in a fancier to-do list app the user might open an inspector view, make multiple changes, then commit them. The app would probably fetch the document properties when the user presses the edit button, let the user take as long as she wants to modify the UI controls, and then save when she returns to the main UI. In this situation, minutes might have gone by, and it’s much more likely that in the meantime the replicator pulled down someone else’s update to that same document.

The easiest way to deal with a conflict is by starting over and trying again. By now the Document will have updated itself to the latest revision, so you’ll be making your changes to current content and won’t get a conflict.

Here’s an example that shows how to handle conflicts. The example makes a copy of the document properties, and then tries to update the document. If the update is successful, the update operation ends. Otherwise, the catch block looks at the returned error code to decide the next action. If the error is a 409 Conflict HTTP status code, it tries to update the document again. If the error contains any other status code, it logs the error and ends the update operation.

boolean done = false;
do {
    Map<String, Object> properties = new HashMap<String, Object>(doc.getProperties());
    try {
        doc.putProperties(properties);
        done = true;
    } catch (CouchbaseLiteException e) {
        if (e.getCBLStatus().getCode() == Status.CONFLICT) {
            // keep trying
        } else {
            e.printStackTrace();
            done = true;
        }
    }
} while (!done);

Deleting documents

Deleting a document is similar to updating a document. Instead of calling putProperties() you call delete() on the document. Here’s an example:

try {
    document.delete();
} catch (Exception e) {
    Log.e ("Main Activity:", "Error deleting document", e);
}

The same complications about conflicts apply to deleting documents. You won’t get a conflict if someone else deleted the document first, but you will if someone modified it. If that happens, you need to decide which takes precedence, and either retry the delete or give up.

Working with views and queries

The basic document API gets you pretty far, but most apps need to work with multiple documents. In a typical app, the top-level UI usually shows either all the documents or a relevant subset of them — in other words, the results of a query.

Querying a Couchbase Lite database involves first creating a view that indexes the keys you’re interested in and then running a query to get the results of the view for the key or range of keys you’re interested in. The view is persistent, like a SQL index.

Because there’s no fixed schema for the view engine to refer to and the interesting bits of a document that we want it to index could be located anywhere in the document (including nested values inside of arrays and subobjects), the view engine has to let us pick through each document to identify the relevant keys and values. That’s what the view’s map function is for: it’s an app-defined function that’s given a document’s contents and returns, or emits, zero or more key-value pairs. These key-value pairs get indexed, ordered by key, and can then be queried efficiently, again by key.

For example, if you have an address book in a database, you might want to query the cards by first or last name, for display or filtering purposes. To do that, you create two views: one to grab the first-name field and return it as the key, and the other to return the last-name field as the key. (And what if you were originally just storing the full name as one string? Then your functions can detect that, split the full name at the space, and return the first or last name. That’s how schema evolution works.)

You might also want to be able to look up people’s names from phone numbers so you can do Caller ID on incoming calls. To do this, make a view whose keys are phone numbers. Now, a document might have multiple phone numbers in it, like the following example JSON document:

{
   "first":"Bob",
   "last":"Dobbs",
   "phone":{
      "home":"408-555-1212",
      "cell":"408-555-3774",
      "work":"650-555-8333"
   }
}

To handle multiple phone numbers, the map function just needs to loop over the phone numbers and emit each one. You then have a view index that contains each phone number, even if several of them map to the same document.

Getting All Documents

To start off, for simplicity, this section shows how you can retrieve all documents in the database without using a view.

To retrieve all the documents in the database, you need to create a Query object. The createAllDocumentsQuery() method in the Database class returns a new Query object that contains all documents in the database:

Query query = database.createAllDocumentsQuery();

After you obtain the new Query object, you can customize it (this is similar to the SQL SELECT statement ORDER BY, OFFSET and LIMIT clauses). The following example shows how to retrieve the ten documents with the highest keys:

query.setLimit(10);
query.setDescending(true);

As a side effect of setting the query to descending order you get the documents in reverse order, but you can compensate for that if it’s not appropriate. Now that the query attributes are set, you can run the query and iterate over the results:

try {
    QueryEnumerator rowEnum = query.run();
    for (Iterator<QueryRow> it = rowEnum; it.hasNext();) {
        QueryRow row = it.next();
        Log.d("Document ID:", row.getDocumentId());

    }
} catch (CouchbaseLiteException e) {
    e.printStackTrace();
}

query.run() evaluates the query and returns a QueryEnumerator object that you can use with loop to iterate over the results. Each result is a QueryRow object. You might expect the result to be a Document, but the key-value pairs emitted in views don’t necessarily correspond one-to-one to documents and a document might be present multiple times under different keys. If you want the document that emitted a row, you can get it from the row’s document ID property.

Creating Views

To create a view, define its map (and optionally its reduce) function. When you define the MapReduce functions, you also assign a version identifier to the function. If you change the MapReduce function later, you must remember to change the version so Couchbase Lite rebuilds the index.

Here’s how the Grocery Sync example app sets up its by-date view:

com.couchbase.lite.View viewItemsByDate = database.getView(String.format("%s/%s", designDocName, byDateViewName));

viewItemsByDate.setMap(new Mapper() {
    @Override
    public void map(Map<String, Object> document, Emitter emitter) {
        Object createdAt = document.get("created_at");
        if (createdAt != null) {
            emitter.emit(createdAt.toString(), document);
        }
    }
}, "1.0");

The name of the view is arbitrary, but you need to use it later on when querying the view. The interesting part here is the Mapper expression, which is a block defining the map function. The block takes the following parameters:

  • A map that has the contents of the document being indexed.
  • A function called emitter that takes the parameters key and value. This is the function your code calls to emit a key-value pair into the view’s index.

After you get that, the example map block is straightforward: it looks for a created_at property in the document, and if it’s present, it emits it as the key, with the entire document contents as the value. Emitting the document as the value is fairly common. It makes it slightly faster to read the document at query time, at the expense of some disk space.

The view index then consists of the dates of all documents, sorted in order. This is useful for displaying the documents ordered by date (which Grocery Sync does), or for finding all documents created within a certain range of dates.

Any document without a created_at field is ignored and won’t appear in the view index. This means you can put other types of documents in the same database (such as names and addresses of of grocery stores) without them messing up the display of the shopping list.

Note

The view index itself is persistent, but the setMap() method must be called every time the app starts, before the view is accessed. You need to call it because the map function is not persistent—it's an ephemeral block pointer that needs to be hooked up to Couchbase Lite at run time.

Querying Views

Now that the view is created, querying it is very much like using createAllDocumentsQuery(), except that you get the Query object from the view rather than the database:

com.couchbase.lite.View view = database.getView("byDate");
Query query = view.createQuery();

Every call to createQuery() creates a new Query object, ready for you to customize. You can set a number of properties to specify key ranges, ordering, and so on, as described in the view and query design section. Then you run the query exactly as described in Getting All Documents.

Updating Queries

It’s useful to know whether the results of a query have changed. You might have generated some complex output, like a fancy graph, from the query rows, and would prefer to save the work of recomputing the graph if nothing has changed. You can accomplish this by keeping the QueryEnumerator object around, and then later checking its stale property. This property returns true if the results are out of date:

if (rowEnum.isStale()) {
    rowEnum = query.run();
}

Using Live Queries

Even better than checking for a query update is getting notified when one happens. Users expect apps to be live and don’t want to have to press a refresh button to see new data. This is especially true if data might arrive over the network at any time through synchronization—that new data needs to show up right away.

For this reason Couchbase Lite has a very useful subclass of Query called LiveQuery, which has a rows property that updates automatically as the database changes. You can register as a listener for immediate notifications when the database changes, and use those notifications to drive user-interface updates.

To create a LiveQuery you just ask a regular query object for a live copy of itself. You can then register as a listener:

private void startLiveQuery(com.couchbase.lite.View view) throws Exception {

    final ProgressDialog progressDialog = showLoadingSpinner();
    if (liveQuery == null) {

        liveQuery = view.createQuery().toLiveQuery();

        liveQuery.addChangeListener(new LiveQuery.ChangeListener() {
            @Override
            public void changed(LiveQuery.ChangeEvent event) {
                displayRows(event.getRows());
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        progressDialog.dismiss();
                    }
                });
            }
        });

        liveQuery.start();

    }
}

Don’t forget to remove the observer when cleaning up.

View and Query Design

This section discusses how to set up some different types of queries.

All Matching Results

If you run a query without setting any key ranges, the result is all the emitted rows, in ascending order by key. To reverse the order, set the query’s descending property to true.

Exact Queries

To get only the rows with specific keys, set the query’s keys property to an array of the desired keys:

List<Object> keyArray = new ArrayList<Object>();
keyArray.add("d123");
keyArray.add("d457");
query.setKeys(keyArray);

The order of the keys in the array doesn’t matter because the results are returned in ascending-key order.

Key Ranges

To get a range of keys, set the query’s startKey and endKey properties. The range is inclusive, that is, the result includes the rows with key equal to endKey.

One common source of confusion is combining key ranges with descending order. Remember that you’re specifying the starting and ending keys, not the minimum and maximum values. In a descending query, the startKey should be the maximum value and the endKey the minimum value.

Compound Keys

The real power of views comes when you use compound keys. If your map function emits arrays as keys, they are sorted as you would expect: the first elements are compared, and if they’re equal the second elements are compared, and so forth. This lets you sort the rows by multiple criteria (like store and item), or group together results that share a criterion.

For example, if a map function emitted the document’s store and item properties as a compound key:

List<Object> compoundKey = new ArrayList<Object>();
compoundKey.add(doc.getProperty("store"));
compoundKey.add(doc.getProperty("item"));
emitter.emit(compoundKey, null);

then the view’s index contains a series of keys ordered like this:

...
["Safeway", "goldfish crackers"]
["Safeway", "tonic water"]
["Trader Joe's", "chocolate chip cookies"]
["Whole Foods", "cruelty-free chakra lotion"]
...
Compound-Key Ordering

The ordering of compound keys depends entirely on how you want to query them; the broader criteria go to the left of the narrower ones. For some queries you might need a different ordering than for others. If so, you need to define a separate view for each ordering. For example, the above ordering is good for finding all the items to buy at a particular store. If instead you want to look up a specific item and see what stores to get it at, you’d want the compound keys in the opposite order. So you could define views called “stores” and “items”, and query whichever one is appropriate.

Compound-Key Ranges

The way you specify the beginning and end of compound-key ranges can be a bit unintuitive. For example, you have a view whose keys are of the form (store, item) and you want to find all the items to buy at Safeway. What are the startKey and endKey? Clearly, their first elements are "Safeway", but what comes after that? You need a way to specify the minimum and maximum possible keys with a given first element. The code to set the start and keys looks like this:

// set the starting key
query.setStartKey("Safeway");

// create empty dictionary object to specify the end of the range
Map<String,Object> eol = new HashMap<String, Object>();

// build the end key in a list object
List<Object> endList = new ArrayList<Object>();
endList.add("Safeway");
endList.add(eol);

// set the ending key
query.setEndKey(endList);

The minimum key with a given first element is just a length-1 array with that element. (This is just like the way that the word “A” sorts before any other word starting with “A”.)

The maximum key contains an empty NSDictionary object. Couchbase Lite defines a sorting or collation order for all JSON types, and JSON objects (also known as dictionaries) sort after everything else. An empty dictionary is kind of like a “Z” on steroids—it’s a placeholder that sorts after any string, number, or array. It looks weird at first, but it’s a useful idiom used in queries to represent the end of a range.

Working With Replication

This section describes how to work with replication in an Android app. To learn more about replication, see Replication in the Couchbase Lite Concepts Guide.

Creating Replications

Replications are represented by Replication objects. You create replication objects by calling the following methods on your local Database object:

  • getPullReplication() sets up a pull replication.
  • getPushReplication() sets up a push replication.

Creating a replication object does not start the replication automatically. To start a replication, you need to send a start message to the replication object.

Newly created replications are noncontinuous. To create a continuous replication, you need to immediately set the continuous property of the Replication object to true.

It’s not strictly necessary to keep references to the replication objects, but you do need them if you want to monitor their progress.

The following example shows how to set up and start a bidirectional replication:

URL syncUrl;
try {
    syncUrl = new URL("http://db.example.com");
} catch (MalformedURLException e) {
    throw new RuntimeException(e);
}

Replication pullReplication = database.createPullReplication(syncUrl);
pullReplication.setContinuous(true);

Replication pushReplication = database.createPushReplication(syncUrl);
pushReplication.setContinuous(true);

pullReplication.start();
pushReplication.start();

Monitoring replication progress

A replication object has several properties you can observe to track its progress. You can use the following Replication class properties to monitor replication progress:

  • completedChangesCount—number of documents copied so far in the current batch
  • changesCount—total number of documents to be copied
  • lastError—set to an NSError if the replication fails or nil if there has not been an error since the replication started.
  • status—an enumeration named ReplicationStatus that indicates the current state of the replication. The status can be STOPPED, OFFLINE, IDLE or ACTIVE. Stopped means the replication is finished or hit a fatal error. Offline means the server is unreachable over the network. Idle means the replication is continuous but there is currently nothing left to be copied. Active means the replication is currently transferring data.

The following example shows how to set up a change listener for a replication:

pullReplication.addChangeListener(new Replication.ChangeListener() {
    @Override
    public void changed(Replication.ChangeEvent event) {
        Replication replication = event.getSource();
        Log.d(TAG, "Replication : " + replication + " changed.");
        if (!replication.isRunning()) {
            String msg = String.format("Replicator %s not running", replication);
            Log.d(TAG, msg);
        }
        else {
            int processed = replication.getCompletedChangesCount();
            int total = replication.getChangesCount();
            String msg = String.format("Replicator processed %d / %d", processed, total);
            Log.d(TAG, msg);
        }
    }
});

Stopping replications

You can cancel continuous replications by stopping them. The following example shows how to stop a replication`:

pullReplication.stop();
pushReplication.stop();

The following example shows how to stop all replications that exist for a database:

List<Replication> replications = database.getAllReplications();
for (Replication rep : replications) {
    rep.stop();
}

Document Validation

Pulling from another database requires some trust because you are importing documents and changes that were created elsewhere. Aside from issues of security and authentication, how can you be sure the documents are even formatted correctly? Your application logic probably makes assumptions about what properties and values documents have, and if you pull down a document with invalid properties it might confuse your code.

The solution to this is to add a validation function to your database. The validation function is called on every document being added or updated. The function decides whether the document should be accepted, and can even decide which HTTP error code to return (most commonly 403 Forbidden, but possibly 401 Unauthorized).

Validation functions aren’t just called during replication—they see every insertion or update, so they can also be used as a sanity check for your own application code. If you forget this, you might occasionally be surprised by getting a 403 Forbidden error from a document update when a change is rejected by one of your own validation functions.

Here’s an example validation function that makes sure each new document contains a towel property:

database.setValidation("hoopy", new Validator() {
    @Override
    public boolean validate(Revision newRevision, ValidationContext context) {
        boolean hoopy = newRevision.isDeletion()  || (newRevision.getProperties().get("towel") != null);
        Log.v("Validator:", String.format("--- Validating %s --> %b", newRevision.getProperties(), hoopy));
        if(!hoopy) {
            context.reject("Where's your towel?");
        }
        return hoopy;
    }
});

The example sets up a validation delegate named hoopy and defines the Validator interface for the validate() method. The validate() method accepts the following parameters:

  • newRevision—a Revision object that contains the new document revision to be approved
  • context— a ValidationContext object that you use to communicate with the database

If the document contained in newRevision is not being deleted, the validate() method checks for the presence of a towel property in the document. If there is no towel property, the method rejects the change by sending a reject() message to the context object.

A validation block can call newRevision.getProperties() to retrieve the document content and determine whether the document is valid. If the revision is invalid, call the reject() method on the context object. You can customize the error returned by specifying an optional error message string with the call to the reject() method.

The example validation block first checks whether the revision is deleted. This is a common idiom: a tombstone revision marking a deletion usually has no properties at all, so it doesn’t make sense to check their values. Another reason to check for deletion is to enforce rules about which documents are allowed to be deleted. For example, suppose you have documents that contain a property named status and you want to disallow deletion of any document whose status property was not first set to completed. Making that check requires looking at the current value of the status property, before the deletion. You can get the currently active revision from the currentRevision property of context. This is very useful for enforcing immutable properties or other restrictions on the changes can be made to a property. The ValidationContext property changedKeys is also useful for checking these types of conditions.

Filtered Replications

You might want to replicate only a subset of documents, especially when pulling from a huge cloud database down to a limited mobile device. For this purpose, Couchbase Lite supports user-defined filter functions in replications. A filter function is registered with a name. It takes a document’s content as a parameter and returns true or false to indicate whether it should be replicated.

Filtered Pull

Filter functions are run on the source database. In a pull, that would be the remote server, so that server must have the appropriate filter function. If you don’t have admin access to the server, you are restricted to the set of already existing filter functions.

To use an existing remote filter function in a pull replication, set the replication’s filter property to the filter’s full name, which is the design document name, a slash, and then the filter name:

pull.setFilter = "grocery/sharedItems";

Filtered pulls enable Couchbase Lite to encode the list of channels it wants Sync Gateway to replicate. In Sync Gateway, the implementation is based on indexes, not filters.

Filtered Push

During a push, the filter function runs locally in Couchbase Lite. Here’s an example of a filter function definition that passes only documents with a "shared" property with a value of true:

database.setFilter("sharedItems", new ReplicationFilter() {
    @Override
    public boolean filter(SavedRevision revision, Map<String, Object> params) {
        return ((Boolean) revision.getProperties().get("shared")).booleanValue();
    }
});

This function can then be plugged into a push replication by name:

push.setFilter = "sharedItems";

Parameterized Filters

Filter functions can be made more general-purpose by taking parameters. For example, a filter could approve documents whose "owner" property has a particular value, allowing the user name to be specified by the replication. That way there doesn’t have to be a separate filter for every user.

To specify parameters, call setFilterParams() on the Replication object. The filter parameters object is a dictionary that maps parameter names to values. The dictionary must be JSON-compatible, so the values can be any type allowed by JSON.

Couchbase Lite filter blocks get the parameters in a map object named params that is passed into the block.

Deleting documents with Filtered Replications

Deleting documents can be tricky in the context of filtered replications. For example, assume you have a document that has a worker_id field, and you set up a filtered replication to pull documents only when the worker_id equals a certain value.

When one of these documents is deleted, it does not get synched in the pull replication. Because the filter function looks for a document with a specific worker_id, and the deleted document won’t contain any worker_id, it fails the filter function and therefore is not synced.

This can be fixed by deleting documents in a different way. Because a document is considered deleted as long as it has the special _deleted field, it is possible to delete the document while still retaining the worker_id field. Instead of using the DELETE verb, use the PUT verb. You definitely need to set the _deleted field for the document to be considered deleted. You can then either retain the fields that you need for filtered replication, like the worker_id field, or you can retain all of the fields in the original document.

Authentication

The remote database Couchbase Lite replicates with likely requires authentication (particularly for a push because the server is unlikely to accept anonymous writes). You need to register login credentials for the replicator to use when logging in to the remote server on your behalf.

Security Tip

Because Basic Authentication sends the password in an easily readable form, it is only safe to use it over an HTTPS (SSL) connection or over an isolated network you're confident has full security. Before configuring authentication, make sure the remote database URL has the https: scheme.

Hard-coded username and password

The simplest but least secure way to store credentials is to use the standard syntax for embedding them in the URL of the remote database:

https://frank:s33kr1t@sync.example.com/database/

The URL in the example specifies the user name frank and password s33kr1t. If you use this form for the remote URL when creating a replication, Couchbase Lite uses the included credentials. The drawback, of course, is that the password is easily readable by anything with access to your app’s data files.

Authenticating with Facebook credentials

  1. Download the Android Facebook SDK.

Here’s an example that shows how to authenticate with Facebook credentials:

private void addFacebookAuthorization(Replication replication) {

    // start Facebook Login
    Session.openActiveSession(this, true, new Session.StatusCallback() {

        // callback when session changes state
        @Override
        public void call(Session session, SessionState state, Exception exception) {

            if (exception != null || !session.isOpened())  {
                return;
            }

            // make request to the Facebook /me API
            Request.executeMeRequestAsync(session, new Request.GraphUserCallback() {

                // callback after Facebook Graph API response with user object
                @Override
                public void onCompleted(GraphUser user, Response response) {
                    if (user != null) {
                        String email = (String) user.getProperty("email");
                        FacebookAuthorizer authorizer = new FacebookAuthorizer(email);
                        authorizer.registerAccessToken(session.getAccessToken(), email, replication.getRemoteUrl());
                        replication.setAuthorizer(authorizer);
                    }

                }
            });

        }
    });
}

Replication Conflicts

Replication is a bit like merging branches in a version control system (for example, pushing and pulling in Git). Just as in version control, you can run into conflicts if incompatible changes are made to the same data. In Couchbase Lite this happens if a replicated document is changed differently in the two databases, and then one database is replicated to the other. Now both of the changes exist there. Here’s an example scenario:

  1. Create mydatabase on device A.
  2. Create document ‘doc’ in mydatabase. Let’s say its revision ID is ‘1-foo’.
  3. Push mydatabase from device A to device B. Now both devices have identical copies of the database.
  4. On device A, update ‘doc’, producing new revision ‘2-bar’.
  5. On device B, update ‘doc’ differently, producing new revision ‘2-baz’. (No, this does not cause an error. The different revision 2 is in a different copy of the database on device A, and device B has no way of knowing about it yet.)
  6. Now push mydatabase from device A to device B again. (Transferring in the other direction would lead to similar results.)
  7. On device B, mydatabase now has two current revisions of ‘doc’: both ‘2-bar’ and ‘2-baz’.

You might ask why the replicator allows the two conflicting revisions to coexist, when a regular PUT doesn’t. The reason is that if the replicator were to give up and fail with a 409 Conflict error, the app would be in a bad state. It wouldn’t be able to resolve the conflict because it doesn’t have easy access to both revisions (the other revision is on the other device). By accepting conflicting revisions, the replicator allows apps to resolve the conflicts by operating only on local data.

What happens on device B now when the app tries to get the contents of ‘doc’? For simplicity, Couchbase Lite preserves the illusion that one document ID maps to one current revision. It does this by choosing one of the conflicting revisions, ‘2-baz’ as the “winner” that is returned by default from API calls. If the app on device B doesn’t look too close, it thinks that only this revision exists and ignores the one from device A.

You can detect conflicts in the following ways:

  • Call -getConflictingRevisions() on the Document object and check for multiple returned values.
  • Create a view that finds all conflicts by having its map function look for a _conflicts property and emit a row if it’s present.

After a conflict is detected, you resolve it by deleting the revisions you don’t want and optionally storing a revision that contains the merged contents. In the example, if the app wants to keep one revision it can just delete the other one. More likely it needs to merge parts of each revision, so it can do the merge, delete revision ‘2-bar’, and put the new merged copy as a child of ‘2-baz’.

Concurrency Support

The Couchbase Lite API is mostly synchronous. That makes it simpler and more efficient. Most of the API calls are quick enough that it’s not a problem to run them on the main thread, but some things might become too slow, especially with large databases, so you might want to offload the processing to a background thread. You have a couple of options.

Asynchronous Queries

View queries slow down as the database grows, especially when the view’s index needs to be updated after the database changes. You can prevent this from blocking your UI by running the query asynchronously.

The easiest way to do this is just to use LiveQuery. It always runs its queries in the background, and then posts a notification on the main thread after the query is complete.

If you use a regular Query object directly, though, you might find that calling the synchronous run() method getting slow. You can access the result rows asynchronously like this:

Future future = query.runAsync(new Query.QueryCompleteListener() {
        @Override
        public void completed(QueryEnumerator rows, Throwable error) {
            // operate on row...
        }
    });

The runAsync() method returns immediately, but performs the query on a background thread. When the query finishes, your callback is called with the query result as its parameter.

Error checking is a bit different. In synchronous mode query.run() throws an exception if there is an error. The async API will call your callback with a Throwable in the case when there was an error during the query.

General-purpose asynchronous calls

Also, there’s a more general-purpose method for doing asynchronous operations, which a few people have asked for. You can now use the Manager object to perform any operation in the background. Here’s an example that deletes a bunch of documents given an array of IDs:

final String[] docids = new String[] {"id1", "id2", "id3"};
 
Future result = manager.runAsync("db", new AsyncTask() {
    @Override
    public boolean run(Database database) {
        for (String docid : docids) {
            database.getDocument(docid).delete();
        }
        return true;
    }
});

Troubleshooting

This section contains information to help you troubleshoot the apps you develop with Couchbase Lite.

Starting Debugging Tests From the Command Line

  1. Set some breakpoints in your test code

  2. ./gradlew connectedInstrumentTest

  3. In Android Studio, go to Run / Attach Debugger to running process

  4. Wait until you see your test process, since it might take a few seconds

  5. Choose it in the “Choose Process” window (it will look something like this)

  6. Wait until it reaches your breakpoint

Running and Debugging a Single Test From Android Studio

These instructions work with either your own application unit tests or the Couchbase lite library code. However, in the case of the latter, you need to depend on the Couchbase Lite code directly rather than using Maven artifacts.

  1. Go to Run / Edit Configurations.

  2. Click the Plus (+) button and choose Android Tests, and give it a name (for example, MyTest).

  3. In the module pulldown menu, choose the module in which your test lives.

    The following figure shows selecting a test in the cblite library.

  4. In the “Test” section, choose the Class radio button.

  5. Under class, add the fully qualified class name that you want to test. For example, com.couchbase.cblite.testapp.tests.Router.

  6. Click Apply or OK

  7. Make sure MyTest is chosen in the configuration pulldown, and choose Run / Debug.

  8. Wait until it reaches your breakpoint.

Running the Test Suite

  1. If Sync Gateway is not already installed, install it by following the instructions in Installing Sync Gateway.

  2. Create a file named config.json and copy the following JSON-formatted configuration data into it:

    {
        "log": ["CRUD", "REST+"],
        "databases": {
           "cblite-test": {
              "server": "walrus:data",
              "sync": function(doc){channel(doc.channels);},
              "users": {
                 "GUEST": {"disabled": false,    "admin_channels": ["*"]}
              }
           }
        }
     }
    
  3. Start Sync Gateway:

     $ ./bin/sync-gateway config.json
    
  4. Create a copy of the test.properties file and name it local-test.properties:

    $ cd CBLite/src/instrumentTest/assets/
     $ cp test.properties local-test.properties
    
  5. Customize the local-test.properties file to point to your database (URL and database name). For example:

    replicationServer=10.0.2.2
     replicationPort=4984
    

    Note: You need to create a local-test.properties file for each of the library projects that contain tests (for example, CBLite and CBLiteJavascript).

    Note: If you are running the tests on the android emulator, then you can use the special 10.0.2.2 address, which has use the IP address of the workstation that launched the emulator (assuming that’s where your server is).

  6. In Android Studio, select Tools > Android > AVD Manager.

    The Android emulator starts.

  7. Launch the test suite:

    $ ./gradlew clean && ./gradlew :CBLite:connectedInstrumentTest && ./gradlew :CBLiteJavascript:connectedInstrumentTest
    

Release Notes

1.0 Beta 2 (January 2014)

This is the second beta release of Couchbase Lite Android 1.0. Couchbase Lite is an ultra-lightweight, reliable, secure JSON database built for all your online and offline mobile application needs. The 1.0 version features native APIs, REST APIs, JSON support, and sync capability. The beta release is available to all community-edition customers.

Features

  • Native API Parity with iOS—The 1.0 feature set available on iOS is now available as native Java APIs on Android. Formal documentation of the Java API set is available here.

Fixes in Beta 2

  • Minimum Android API level support

    • We now support Gingerbread (API level 9) and above

    Issues: 115

  • Attachment support

    • Retrieval of an Attachment’s body is now working correctly
    • Deletion of an Attachment via the REST API is now available

    Issues: 134, 152

  • Database support

    • Attempts to create databases with invalid names now throws correct status code
    • A local or remotely replicated document update is now updated in the database document cache and does so by document ID

    Issues: 146, 164

  • Document support

    • _all_docs keys parameter is now supported
    • missing property now working properly to allow for compaction verification

    Issues: 147, 155

Known Issues

  • Performance

    • There are known performance issues that we plan to fix before general release. These performance issues include slowness in replication and in view creation.

    Issues: 123, 125, 126

  • Document support

    • _bulk_docs does not include document deletions as part of push replication to target databases and causes conflicting revision trees

    Issues: 174

1.0 Beta (13 September 2013)

This is the Beta release of Couchbase Lite Android 1.0.

Couchbase Lite is an ultra-lightweight, reliable, secure JSON database built for all your online and offline mobile application needs. This version features REST APIs, JSON support, and sync capability.

The beta release is available to all community-edition customers.

Features

  • REST APIs—REST APIs provide an alternative access method based on your development needs.

  • JSON support—Use a flexible data model designed for mobile object-oriented apps. Adapt to your application needs with immediacy and little impact.

  • Easy sync with Couchbase Sync Gateway—Get sync-ready with just a few lines of code. Focus on application development, not syncing.

  • Peer-to-peer support via REST APIs—Communicate with nearby devices, even offline, with our REST API-enabled P2P support.

Fixes

None.

Known Issues

  • Changes Feed

    • Change Tracker is not using streaming JSON parser for parsing the changes feed for continuous replications.

    Issue: 97

  • Document support

    • Saving large documents with REST often returns an HTTP error 400 due to an incorrect check stop in a method. The method checks to see if there is data to be read in the InputStream, rather than checking whether the stream is empty.
    • Occassional error 400 outputted when attempting to do a PUT

    Issue: 95, 99

  • Attachment support

    • Updates to documents with attachments are resending attachments even when the attachment data hasn’t been updated. This causes performance issues.

    Issue: 66