Skip to content

khanium/quickstart-mobile-client

Repository files navigation

Offline-first & edge-computing Solutions on Java

Simple Mobile, Offline-first & edge-compunting quickstart tutorial using Java Couchbase Lite +3.2.2 / Sync Gateway/App Service +3.2.2 / Couchbase Server/Capella 7.6.5

QuickStart

Prerequisites

Build

git clone https://github.com/khanium/quickstart-mobile-client.git
cd quickstart-mobile-client
git submodule init
git submodule update --remote --recursive

This will initialize and update the quickstart-sync-gateway submodule.

Setup & Deploy Docker Compose Environment

cd quickstart-environment
docker compose build --no-cache 
docker compose up -d

Note: docker compose build --no-cache is only required if you change the dockerfile or the docker-compose.yml file. Otherwise, you can just run docker compose up -d to start the environment.

docker compose up -d will deploy a local environment with:

  • Couchbase Server 7.6.5 - localhost:8091
    • Users: [ Administrator, sync_gateway, metrics_user ]
    • Buckets:
      • Bucket: demo
        • Scope: custom
          • Collections: [typeA,typeB, typeC ]
      • Bucket: nonmobilebucket
  • Sync Gateway 3.2.2 / App Service - localhost:4984
    • Database / Endpoint: db
      • Users: [ userdb1/Password1!]
        • Scope: custom
          • Collections: [typeA, typeB, typeC]
            • sync function / access control (per collection):
              function(doc, oldDoc, meta) {
                if(doc._deleted) {
                  // not allow to delete documents from device unless you have admin role
                  requireRole("admin"); // throw an error unless the user has the "admin" role
                  return ;
                }
            
                if(doc.channels) {  
                  channel(doc.channels);
                } else {
                  //define channels field into the model entity otherwise throw an error ...
                  throw({forbidden: "document '"+doc._id+"' doesn't contain channels field to sync"});
                }
              }
  • Prometheus - localhost:9090
    • Metrics: Couchbase Sync Gateway / Couchbase Server
  • Grafana. - localhost:3000
    • Dashboard: Couchbase Sync Gateway
    • Dashboard: Couchbase Server

Run the Mobile Client

Wait until the Sync Gateway is up and running. You can check the service is UP with: http://localhost:4984/

Then, run the mobile client:

cd quickstart-mobile-client
gradle build
./gradlew run

Overview

Mobile Client

  1. Prerequisite - Initialize CouchbaseLite loading the native libraries
  2. Setup Local Device Database
    1. Setup Collections
  3. Setup Replicator
    1. Add replicator status change listener
  4. Start Replicator synchronization

Dependencies

repositories {
    mavenCentral()
    maven {url 'https://mobile.maven.couchbase.com/maven2/dev/'}
}

dependencies {
    ...
    implementation 'com.couchbase.lite:couchbase-lite-java-ee:3.2.2'
    ...
}

CouchbaseLite Java EE

Basic Main

  1. Prerequisite - Initialize CouchbaseLite loading the native libraries
    static {
        System.out.println("Starting CouchbaseLite at " + LocalTime.now());
        com.couchbase.lite.CouchbaseLite.init();
    }
  1. Setup Local Device Database
    DatabaseConfiguration config = new DatabaseConfiguration();
    config.setDirectory("data/"+username);
    Database database = new Database(database, config);

1.1. Setup Collections

    Collection colA = database.createCollection("typeA", scope); // if the collection exists, it returns the collection otherwise creates a new one
    Collection colB = database.createCollection("typeB", scope); //Collection default = database.getDefaultCollection();
  1. Setup Replicator
    URI syncGatewayUri = new URI("ws://localhost:4984/"+database);
    ReplicatorConfiguration replConfig = new ReplicatorConfiguration(new URLEndpoint(syncGatewayUri));
    replConfig.setType(PUSH_AND_PULL);
    replConfig.setAutoPurgeEnabled(true);
    replConfig.setAuthenticator(new BasicAuthenticator(username, password.toCharArray())); 
    replConfig.setContinuous(true);
    CollectionConfiguration collectionConfiguration = new CollectionConfiguration();
    collectionConfiguration.setConflictResolver(ConflictResolver.DEFAULT);
    replConfig.addCollections(List.of(colA, colB), collectionConfiguration);  

2.1. Add replicator status change listener

    replicator.addChangeListener(change -> {
        if (change.getStatus().getError() != null) {
            System.err.println("Error in replication ("+change.getStatus().getActivityLevel()+"): " + change.getStatus().getError().getMessage());
            // TODO handling error here if proceed
        } else {
            System.out.println("Replication in progress: " + change.getStatus().getActivityLevel());
            // TODO handling status change & progress notifications here
        }
        // checking for final or idle replication states...
        if(change.getStatus().getActivityLevel().equals(ReplicatorActivityLevel.IDLE) || change.getStatus().getActivityLevel().equals(ReplicatorActivityLevel.STOPPED)){
            printCount(database);
            // checking for final replication states here
            if (change.getStatus().getActivityLevel().equals(ReplicatorActivityLevel.STOPPED)) {
                System.err.println("Replication stopped unexpectedly!");
                exitApp(); // TODO instead exiting to handle replication exception here. I.e: retry to start the replication `change.getReplicator().start();` with a maximum number of retry...
            }
        }
    });
  1. Start Replicator synchronization
    replicator.start();

All the above code is in the SimpleClientApp.java class.

SimpleClientApp.java

  static {
    // 0. Initialize CouchbaseLite - Prerequisite - it loads the native libraries
        System.out.println("Starting CouchbaseLite at " + LocalTime.now());
        com.couchbase.lite.CouchbaseLite.init();
    }

    public static void main(String[] args) throws CouchbaseLiteException, URISyntaxException {
        String username = System.getProperty("user.name") != null ? System.getProperty("user.name") : "userdb1";
        String password = System.getProperty("user.password") != null ? System.getProperty("user.password") : "Password1!";
        Array channels = new MutableArray().addString("store0001").addString("blue");
        String scope = "custom";
        String database = "db";
        // 1. Setup Database
        DatabaseConfiguration config = new DatabaseConfiguration();
        config.setDirectory("data/"+username);
        Database database = new Database(database, config);

        // 1.1.  if they don't exist, it creates collections to replicate
        Collection colA = database.createCollection("typeA", scope); // if the collection exists, it returns the collection otherwise creates a new one
        Collection colB = database.createCollection("typeB", scope); //Collection default = database.getDefaultCollection();

        // 2. Setup Replicator
        URI syncGatewayUri = new URI("ws://localhost:4984/"+database);
        ReplicatorConfiguration replConfig = new ReplicatorConfiguration(new URLEndpoint(syncGatewayUri));
        replConfig.setType(PUSH_AND_PULL);
        replConfig.setAutoPurgeEnabled(true);
        replConfig.setAuthenticator(new BasicAuthenticator(username, password.toCharArray())); 
        replConfig.setContinuous(true);
        CollectionConfiguration collectionConfiguration = new CollectionConfiguration();
        collectionConfiguration.setConflictResolver(ConflictResolver.DEFAULT);
        replConfig.addCollections(List.of(colA, colB), collectionConfiguration);  
        Replicator replicator = new Replicator(replConfig);
        // 2.1. Setup replicator status change listener
        replicator.addChangeListener(change -> {
            if (change.getStatus().getError() != null) {
                System.err.println("Error in replication ("+change.getStatus().getActivityLevel()+"): " + change.getStatus().getError().getMessage());
                // TODO handling error here if proceed
            } else {
                System.out.println("Replication in progress: " + change.getStatus().getActivityLevel());
                // TODO handling status change & progress notifications here
            }
            // checking for final or idle replication states...
            if(change.getStatus().getActivityLevel().equals(ReplicatorActivityLevel.IDLE) || change.getStatus().getActivityLevel().equals(ReplicatorActivityLevel.STOPPED)){
                printCount(database);
                // checking for final replication states here
                if (change.getReplicator().getConfig().isContinuous() && change.getStatus().getActivityLevel().equals(ReplicatorActivityLevel.STOPPED)) {
                    System.err.println("Replication stopped unexpectedly!");
                    exitApp(); // TODO instead exiting to handle replication exception here. I.e: retry to start the replication `change.getReplicator().start();` with a maximum number of retry...
                }
            }
        });
        // 3. Start Replicator synchronization
        replicator.start();
    }

    private static void exitApp(){
        System.err.println("Exiting...");
        System.exit(0);
    }

    private static void printCount(Database database) {
        System.out.println("Documents in the local database: ");
        try {
            //System.out.println(" - _default._default: "+ database.getCollection("_default","_default").getCount()+" documents");
            System.out.println(" - custom.typeA: "+ database.getCollection("typeA","custom").getCount()+" documents");
            System.out.println(" - custom.typeB: "+ database.getCollection("typeB","custom").getCount()+" documents");
        } catch (CouchbaseLiteException e) {
            throw new RuntimeException(e);
        }
    }

Simple Application and Command Line Menu

In this MobileClientDemoApplication.java main, you can find a simple example of how to create a simple mobile client with a replicator to sync with the Sync Gateway from a property file values using springboot framework.

On top, a command line action menu to create a document, update it, delete it, list all documents in the local database or create your own submenu for your use case.

MainMenu.java

** *************************************** **
** Main Menu Menu: 
      0. Exit
      1. CRUD Operations on documents
      2. List All documents
      3. Custom Use Cases
      4. Start Replication
      5. Count documents in the local database
      6. Choose the default working collection (`custom.typeA`)
** 
** Please enter your choice: 

Application.yaml properties

resources/application.yaml

couchbase:
  remote:
    endpoint-url: ws://127.0.0.1:4984/db
    # endpoint-url: wss://ecfyee1wi6dvwova.apps.cloud.couchbase.com:4984/db
    # certificate-path: assets/cert.pem
    continuous: true
    replicator-type: PUSH_AND_PULL
    reset-checkpoint: false
    websocket:
      timeout: 10000
      heartbeat: 15000
    collections:
      # _default:
      #  documentIDs-filter:
      #  channels-filter:
      typeA:
        documentIDs-filter:
        channels-filter:
      typeB:
        documentIDs-filter:
        channels-filter:
    authenticator:
      username: userdb1
      password: Password1!
  local:
    database: db
    db-path: data
    flush-previous-db: true
    auto-purge: true
    scope:
      name: custom
      collections: typeA,typeB
    #   name: _default
    #   collections: _default
  log:
    path: logs
    level: debug
    max-size: 100000000
    rotation-count: 10
    plaintext: true

Properties files are defined in the src/main/resources folder. You can define your own properties file and pass it as a command line argument to the application. This file contains the following structure:

couchbase:
  remote: 
    # set the remote database properties (endpoint-url, certificate-path, collections, etc) for syncing
  local:
    # set the local database properties (path, name, scope, collections, etc)
    # note: the collections should contain all collections defined in the replicator collections properties
  log:
    # set the logs properties (path, level to debug,etc)

In this terminology, remote database is the Couchbase Server / Sync Gateway and local database is the Couchbase Lite device database.

Config Server Environment Deployment

TBD

Local Docker Compose

Capella App Service

What next?

TBD

About

Simple Mobile quickstart tutorial using Java Couchbase Lite +3.2.2 / Sync Gateway/App Service +3.2.2 / Couchbase Server/Capella 7.6.5

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages