User Profile Sample: Data Sync Fundamentals


Couchbase Sync Gateway is a key component of the Couchbase Mobile stack. It is an internet-facing synchronization mechanism that securely syncs data across devices as well as between devices and the cloud. Couchbase Mobile 2.x introduces a brand new websockets based replication protocol.

The core functions of the Sync Gateway include

  • Data Synchronization across devices and the cloud

  • Authorization & Access Control

  • Data Validation

This tutorial will demonstrate how to -

  • Setup the Couchbase Sync Gateway (in walrus mode) to sync content between multiple Couchbase Lite enabled clients. We will will cover the basics of the Sync Gateway Configuration.

  • Configure your Sync Gateway to enforce data routing, access control and authorization. We will cover the basics of Sync Function API

  • Configure your Couchbase Lite clients for replication with the Sync Gateway

  • Use "Live Queries" or Query events within your Couchbase Lite clients to be asyncronously notified of changes

We will be using a Android app as an example of a Couchbase Lite enabled client.

You can learn more about the Sync Gateway here


This tutorial assumes familiarity with building Android apps using Java and with the basics of Couchbase Lite.

  • If you are unfamiliar with the basics of Couchbase Lite, it is recommended that you walk through the following tutorials

    • Fundamentals of using Couchbase Lite 2.6 as a standalone database

    • Query Basics with a prebuilt version of Couchbase Lite database

  • Android Studio 3.4 or above

  • Android device or emulator running API level 21 or above

  • Android SDK 26

  • Android Build Tools 26

  • JDK 8

  • git (Optional) This is required if you would prefer to pull the source code from GitHub repo.

  • curl HTTP client

    • You could use any HTTP client of your choice. But we will use curl in our tutorial. Download latest version from curl website

System Overview

We will be working with a simple "User Profile" app which we introduced in the Fundamentals Tutorial and extended in the Query Tutorial.

In this tutorial, we will be extending that app to support data sync.

The app does the following

  • Allows users to log in and create or update his/her user profile information. The user profile view is automatically updated everytime the profile information changes in the underlying database

  • The user profile information is synced with a remote Sync Gateway which then syncs it to other devices (subject to access control and routing configurations specified in the sync function)

App with Sync

App Installation

Fetching App Source Code

You have two options

Option 1 : Git Clone

  • Clone the sync branch of the User Profile Demo project from GitHub. Type the following command in your terminal

      git clone -b sync

Option 2 : Download .zip

  • Download the User Profile Demo solution directly from here.

Once all of the solution bits have been retrieved you can verify the installation, and run the app!

Installing Couchbase Lite

  • This sample project already contains the appropriate additions for downloading, and utilizing the Android Couchbase Lite dependency module. However, in the future, to include Couchbase Lite support within an Android app add the the following within app/build.gradle.

      dependencies {
        implementation 'com.couchbase.lite:couchbase-lite-android-ee:2.5.0'

Try it out

  • Open build.gradle using Android Studio.

  • Build and run the project.

  • Verify that you see the login screen.

    User Profile Login Screen Image

Sync Gateway 2.x Installation

There are several deployment options for the Sync Gateway. In our tutorial, we will be using the Sync Gateway installer to install the Sync Gateway on the same localhost as the mobile app.

Download the Installer

  • Download the latest Sync Gateway 2.x installer from Downloads page. Be sure to select the "Mobile" tab.

  • Launch the Sync Gateway from the command line with the sync-gateway-config-userprofile-android-walrus.json config file. This file is bundled with the User Profile mobile App source that you downloaded as per instructions in Fetching App Source Code section. The sync-gateway-config-userprofile-android-walrus.json will be located in the /path/to/UserProfileDemo/modules/userprofile/examples/src folder

    Type following commands in the command terminal -

    cd  /path/to/sync-gateway-installation/couchbase-sync-gateway/bin
    ./sync_gateway /path/to/UserProfileDemo/modules/userprofile/examples/src/sync-gateway-config-userprofile-android-walrus.json
  • You should see a bunch of logs output to the console, similar to one below. For brevity, some of the log messages have been trimmed from output below

    ~/couchbase-sync-gateway/bin | => ./sync_gateway ~/projects/android/UserProfileDemo/modules/userprofile/examples/src/sync-gateway-config-userprofile-android-walrus.json
    2018-05-07T15:25:02.924-04:00 Enabling logging: [*]
    2018-05-07T15:25:02.924-04:00 ==== Couchbase Sync Gateway/2.0.0(832;2d8a6c0) ====
    2018-05-07T15:25:03.028-04:00     Created user ""
    2018-05-07T15:25:03.028-04:00 Starting admin server on
    2018-05-07T15:25:03.028-04:00 Changes+: Notifying that "userprofile" changed (keys="{}") count=2
    2018-05-07T15:25:03.028-04:00 Cache: Received #1 ("_user/")
    2018-05-07T15:25:03.028-04:00 Cache: Initialized cache for channel "*" with options: &{ChannelCacheMinLength:50 ChannelCacheMaxLength:500 ChannelCacheAge:1m0s}
    2018-05-07T15:25:03.028-04:00 Cache:     #1 ==> channel "*"
    2018-05-07T15:25:03.028-04:00 Changes+: Notifying that "userprofile" changed (keys="{*}") count=3
    2018-05-07T15:25:03.031-04:00 Starting server on :4984 ...

Now, let’s verify the installation

Try it Out

  • Open a browser and enter http://localhost:4984 in the address bar

  • You should see a message similar one below

    {"couchdb":"Welcome","vendor":{"name":"Couchbase Sync Gateway","version":"2.0"},"version":"Couchbase Sync Gateway/2.0.0(832;2d8a6c0)"}

Sample App Architecture

The sample app follows the MVP pattern, separating the internal data model, from a passive view through a presenter that handles the logic of our application and acts as the conduit between the model and the view.

MVP Architecture

In the Android Studio project, the code is structured by feature. You can select the Android option in the left navigator to view the files by package.

MVP Android Studio

Each package contains 3 different files:

  • Activity: This is where all the view logic resides.

  • Presenter: This is where all the business logic resides to fetch and persist data to a web service or the embedded Couchbase Lite database.

  • Contract: An interface that the Presenter and Activity implement.

MVP Package

Data Model

If have followed along the tutorial on Query Basics, you can skip this section and proceed to the Sync Gateway Configuration. section We have not made any changes to the Data model for this tutorial. Couchbase Lite is a JSON Document Store. A Document is a logical collection of named fields and values.The values are any valid JSON types. In addition to the standard JSON types, Couchbase Lite supports some special types like Date and Blob. While it is not required or enforced, it is a recommended practice to include a "type" property that can serve as a namespace for related.

The "User Profile" Document

The app deals with a single Document with a "type" property of "user". The document ID is of the form "user::<email>". An example of a document would be

    "name":"Jane Doe",
    "address":"101 Main Street",
    "image":CBLBlob (image/jpg),
    "university":"Missouri State University"


For the purpose of this tutorial the "user" Document is first stored within an Object of type Map<String, Object>.

Map<String, Object> profile = new HashMap<>();
profile.put("name", nameInput.getText().toString());
profile.put("email", emailInput.getText().toString());
profile.put("address", addressInput.getText().toString());
profile.put("university", universityText.getText().toString());
profile.put("type", "user");
byte[] imageViewBytes = getImageViewBytes();

if (imageViewBytes != null) {
    profile.put("imageData", new com.couchbase.lite.Blob("image/jpeg", imageViewBytes));

The "University" Document

The app comes bundled with a collection of Documents of type "university". Each Document represents a university.

    "type":"university","web_pages": [
    "name": "Missouri State University",
    "alpha_two_code": "US",
    "state-province": MO,
    "domains": [
    "country": "United States"

The University Record

When "university" Document is retrieved from the database it is stored within an Object of type Map<String, Object>.

Map<String, Object> properties = new HashMap<>();
properties.put("name", row.getDictionary("universities").getString("name"));
properties.put("country", row.getDictionary("universities").getString("country"));
properties.put("web_pages", row.getDictionary("universities").getArray("web_pages"));

Sync Gateway Configuration

The Sync Gateway Configuration determines the run time behavior of the Sync Gateway and is typically specified in a JSON file. You can also use the Sync Gateway Config REST Endpoint to specify the configuration. In our application, we will be defining it in the sync-gateway-config-userprofile-android-walrus.json file.

  • Open the sync-gateway-config-userprofile-android-walrus.json file using any text editor of your choice. The sync-gateway-config-userprofile-android-walrus.json is located in the app bundle at /path/to/UserProfileDemo/modules/userprofile/examples/src.

    Locate the following settings in the configuration file -

  • The users setting.

    The list of users recognized by the Sync Gateway is specified using the users config setting. The Sync Gateway will only authorize syncronization requests from valid/recognized users.

    For simplicity, we have hardcoded the user to be "" and the password of "password". In a production app, you would likely configure the user dynamically on the Sync Gateway through the User Admin REST API instead of hardcoding it this way.

    "userprofile": {
          "users": { "": { "password": "password"} },
    If you want to want to use a different user, then add the credentials of that user to this configuration setting and restart the Sync Gateway.
  • The server setting

    This is where we specify the URL of the server. We have specified this to be walrus. This is an in-memory-only mode that is only recommended for development environments. We will be configuring Sync Gateway to operate in this mode. In a production deployment, the Sync Gatway should be backed up by a Couchbase Server.

    This is specified using the server config setting

    "userprofile": {
          "server": "walrus:",

You can learn more about the walrus mode in this guide.

Sync Function

The Sync Function is a Javascript function that is specified as part of the Sync Gateway Configuration. The Sync Function handles data validation, authorization, access control and data routing.

  • Open the sync-gateway-config-userprofile-android-walrus.json file using any text editor of your choice. The sync-gateway-config-userprofile-android-walrus.json is located in the app bundle at /path/to/UserProfileDemo/modules/userprofile/examples/src.

  • Locate the sync setting and follow along with the rest of the sections below


We use the requireUser() API to verify that the email property specified in the Document matches the Id of the user making the request. The Id of the user making the request is specified in the Authorization header. We will be using Basic Authentication in our application.

function sync(doc, oldDoc) {
   /* Authorization */

  // Verify the user making the request is the same as the one in doc's email

Data Validation

In this case, we are doing some basic validation of the contents of the Document

function sync(doc, oldDoc) {
   /* Data Validation */

   if (!isDelete()) {
      // Validate the presence of email fields
      validateNotEmpty("email",; (1)

      // Check if document is being created / added for first time
      // We allow any user to create the document
      if (isCreate()) {

        // Validate that the document Id _id is prefixed by owner.
        var expectedDocId = "user" + "::" +;

        if (expectedDocId != doc._id) { (2)
            throw({forbidden: "user doc Id must be of form user:email"});

      } else {
         // Validate that the email hasn't changed.
        validateReadOnly("email",,; (3)


  // Verify that specified property exists
  function validateNotEmpty(key, value) {
    if (!value) {
      throw({forbidden: key + " is not provided."});

  // Verify that specified property value has not changed during update
  function validateReadOnly(name, value, oldValue) {
    if (value != oldValue) {
      throw({forbidden: name + " is read-only."});
1 Verify that the email property is not null. If it’s null, we throw a JS exception (see validateNotEmpty() function)
2 If this a new document, then verify that the Id of the Document is of the required format (i.e. "user::<email>"). We throw an exception if that’s not the case.
3 If this is a document update, then verify that the email property value has not changed. Again, we throw an exception if that’s not the case.

You can learn more about the Sync Function in this guide

Data Routing

channels are a mechanism to "tag" documents and is typically used to seggregate documents based on the contents of the document. Combined with access API and requireAccess API, it can be used to enforce Access Control. As we shall see in a later section, clients can use channels to pull only a subset of documents.

  /* Routing */
  // Subsequent updates to document must be authorized
  var email = getEmail();

  // Add doc to the user's channel.
  channel("channel." + email); (1)

  // get email Id property
  function getEmail() {
    return (isDelete() ? :;
1 The channel comes into existance the first time a document is added to it. In our case, the channel name is generated from the email property specified in the document

Access Control

You can enforce access control to channels using the access API. This will ensure that only users with access to a specific channel will be able to retrieve documents in the channel.

  /* Access Control */
  // Give user read access to channel
   if (!isDelete()) {
    // Deletion of user document is essentially deletion of user
       access(email,"channel." + email)

Starting Replication

Two-way Replication between the app and the Sync Gateway is enabled when user logs into the app.

  • Open the file and locate the startPushAndPullReplicationForCurrentUser() function.

    public static void startPushAndPullReplicationForCurrentUser(String username, String password)
  • Next, we create an instance of the ReplicatorConfiguration instance that specifies the source and target database and you can optionally, override the default configuration settings.

    ReplicatorConfiguration config = new ReplicatorConfiguration(userprofileDatabase, new URLEndpoint(url)); (1)
    config.setReplicatorType(ReplicatorConfiguration.ReplicatorType.PUSH_AND_PULL); (2)
    config.setContinuous(true); (3)
    config.setAuthenticator(new BasicAuthenticator(username, password)); (4)
    config.setChannels(Arrays.asList("channel." + username)); (5)
    1 Initialize with source as the local Couchbase Lite database and the remote target as the Sync Gateway
    2 Replication type of PUSH_AND_PULL indicates that we require two-way sync. A value of .PUSH specifies that we only pull data from the Sync Gateway. A value of .PULL specifies that we only push data.
    3 The continuous mode is specified to be true which means that changes are synced in real-time. A value of false which implies that data is only pulled from the Sync Gateway.
    4 This is where you specify the authentication credentials of the user. In the Authorization section, we discussed that the Sync Gateway can enforce authorization check using the requireUser API.
    5 The channels are used to specify the channels to pull from. Only documents belonging to the specified channels are synced. This is subject to Access Control rights enforced at the Sync Gateway. This means that if a client does not have access to documents in a channel, the documents will not be synched even if the client specifies it in the replicator configuration.
  • Initialize the Replicator with the ReplicatorConfiguration

    replicator = new Replicator(config);
  • We attach a callback listener to the Replicator to be asynchronously notified of state changes. This could be useful for instance, to inform the user of the progress of the replication. This is an optional step

    replicatorListenerToken = replicator.addChangeListener(new ReplicatorChangeListener() {
        public void changed(ReplicatorChange change) {
            if (change.getReplicator().getStatus().getActivityLevel().equals(Replicator.ActivityLevel.IDLE)) {
                Log.e("Replication Comp Log", "Scheduler Completed");
            if (change.getReplicator().getStatus().getActivityLevel().equals(Replicator.ActivityLevel.STOPPED)
                    || change.getReplicator().getStatus().getActivityLevel().equals(Replicator.ActivityLevel.OFFLINE)) {
                Log.e("Rep Scheduler  Log", "ReplicationTag Stopped");
  • Start the replicator


Stopping Replication

When user logs out of the app, the replication is stopped before the database is closed.

  • Open the file and locate the stopAllReplicationForCurrentUser() function.

    public static void stopAllReplicationForCurrentUser()
  • Stop the replicator and remove any associated change listeners

    All open replicators must be stopped before database is closed. There will be an exception if you attempt to close the database without closing the active replicators.

Query Events / Live Queries

In couchbase Lite 2.0, the app can set up live queries in order to be asynchronously notified of changes to the database that affect the results of the query. This would be very useful for instance, to keep a UI View up-to-date with the results of a query.

In our app, the user profile view is kept up-to-date with a live query that fetches the user profile data that is used to populate the view. This means that, if the replicator pulls down changes to the user profile, it will be automatically reflected in the view.

  • Open the file and locate the fetchProfile() function.

     public void fetchProfile()
  • Build the Query using QueryBuilder API. If you are unfamiliar with this API, please check out this tutorial.

    Query query = QueryBuilder
                    .where(; (1)
    1 We query for documents based on document Id. In our app, there should be exactly one user profile document corresponding to this Id.
  • Attach listener callback to the query to make it live

    query.addChangeListener(new QueryChangeListener() {
        public void changed(QueryChange change) { (1)
            ResultSet rows = change.getResults();
            Result row = null;
            Map<String, Object> profile = new HashMap<>(); (2)
            profile.put("email", DatabaseManager.getSharedInstance().currentUser);
            while ((row = != null) {
                Dictionary dictionary = row.getDictionary("userprofile"); (3)
                if (dictionary != null) {
                    profile.put("name", dictionary.getString("name")); (4)
                    profile.put("address", dictionary.getString("address")); (4)
                    profile.put("imageData", dictionary.getBlob("imageData")); (4)
                    profile.put("university", dictionary.getString("university")); (4)
                    profile.put("type", dictionary.getString("type")); (4)
    1 Attach a listener callback to the query. Attaching a listerner automatically makes it live so any time there is a change in the user profile data in the underlying database, the callback would be invoked
    2 Create an instance of [UserRecord]. This will be populated with the query results.
    3 The SelectResult.all() method is used to query all the properties of a document. In this case, the document in the result is embedded in a dictionary where the key is the database name, which is "userprofile". So we retrieve the Dictionary at key "userprofile".
    4 We use appropriate type getters to retrieve values and populate the UserRecord instance


Exercise 1

In this exercise, we will observe how changes made on one app are synced across to the other app

  • The app should be running in two simulators side by side

  • Log into both the simulators with same userId and password. Use the values "" and "password" for user Id and password fields respectively

  • On one simulator, enter values in the user and address fields.

  • Confirm that changes show up in the app on the other simulator.

  • Similarly, make changes to the app in the other simulator and confirm that the changes are synced over to the first simulator.

Exercise 2

In this exercise, we will observe changes made via Sync Gateway are synced over to the apps

  • Make sure you complete Exercise 1. This is to ensure that you have the appropriate user profile document (with document Id of "user::<emailId>") created through the app and synced over to the Sync Gateway.

  • Open the command terminal and issue the following command to get the user profile document via GET Document REST API . We will be using curl to issue the request. If you haven’t done so, please install curl as indicated in the Prerequisites section

    curl -X GET \
      http://localhost:4985/userprofile/ \
      -H 'Accept: application/json' \
      -H 'Cache-Control: no-cache' \
      -H 'Content-Type: application/json'
  • Your response should look something like the response below. The exact contents depends on the user profile information that you provided via your mobile app.

        "_attachments": { (2)
            "blob_1": {
                "content_type": "image/jpeg",
                "digest": "sha1-S8asPSgzA+F+fp8/2DdIy4K+0U8=",
                "length": 14989,
                "revpos": 2,
                "stub": true
        "_id": "",
        "_rev": "2-3a76cfa911e2c54d1e82b29dbffc7f4e5a9bc265", (1)
        "address": "",
        "email": "",
        "image": {
            "@type": "blob",
            "content_type": "image/jpeg",
            "digest": "sha1-S8asPSgzA+F+fp8/2DdIy4K+0U8=",
            "length": 14989
        "name": "",
        "type": "user",
        "university": "Missouri State University"
    1 Record the revision Id of the document. You will need this when you update the document
    2 If you had updated an image via the mobile app, you should see an * "_attachments"* property. This entry holds an array of attachments corresponding to each image blob entry added by the mobile app. This property is added by the Sync Gateway when it processes the document. You can learn more about how image Blob types are mapped to attachments here.
  • In the command terminal, issue the following command to update the user profile document via PUT Document REST API

    curl -X PUT \
      'http://localhost:4985/userprofile/' \
      -H 'Accept: application/json' \
      -H 'Cache-Control: no-cache' \
      -H 'Content-Type: application/json' \
      -d '{
        "address": "101 Main Street", (1)
        "email": "",
        "image": {
            "@type": "blob",
            "content_type": "image/jpeg",
            "digest": "sha1-S8asPSgzA+F+fp8/2DdIy4K+0U8=",
            "length": 14989
        "name": "",
        "type": "user",
        "university": "Missouri State University"
    1 I updated the university field via the REST API. You can choose to update any other profile information
  • Confirm that you get a HTTP "201 Created" status code

  • As soon as you update the document via the Sync Gateway REST API, confirm that the changes show up in the mobile app on the simulator.

    App Sync
From Exercise 2 above, you observed that changes made on the Sync Gateway are propagated to the Couchbase Lite clients. However, if you tried to update the attachment / image using the Attachments REST API, you would not see the image getting updated on the client side. This is a known issue in 2.0 and should be fixed in the next release.

Handling Conflicts during Data Synchronization

Data conflicts are inevtiable in an environment where you can potentially have multiple writes updating the same data concurrently. Couchbase MObile 2.0 supports Automated Conflict Resolution.

You can learn more about automated conflict resolution in this blog post.

Learn More

Congratulations on completing this tutorial!

This tutorial walked you through an example of how to use a Sync Gateway to synchronize data between Couchbase Lite enabled clients. We discussed how to configure your Sync Gateway to enforce relevat access control, authorization and data routing between Couchbase Lite enabled clients.

Check out the following links for further details