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
git clone https://github.com/khanium/quickstart-mobile-client.git
cd quickstart-mobile-client
git submodule init
git submodule update --remote --recursiveThis will initialize and update the quickstart-sync-gateway submodule.
cd quickstart-environment
docker compose build --no-cache
docker compose up -dNote: 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]
- Collections: [
- Scope:
- Bucket:
nonmobilebucket
- Bucket:
- Users: [
- 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"}); } }
- Collections: [
- Scope:
- Users: [
- Database / Endpoint:
- Prometheus - localhost:9090
- Metrics:
Couchbase Sync Gateway/Couchbase Server
- Metrics:
- Grafana. - localhost:3000
- Dashboard:
Couchbase Sync Gateway - Dashboard:
Couchbase Server
- Dashboard:
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- Prerequisite - Initialize CouchbaseLite loading the native libraries
- Setup Local Device Database
- Setup Collections
- Setup Replicator
- Add replicator status change listener
- Start Replicator synchronization
repositories {
mavenCentral()
maven {url 'https://mobile.maven.couchbase.com/maven2/dev/'}
}
dependencies {
...
implementation 'com.couchbase.lite:couchbase-lite-java-ee:3.2.2'
...
}- Prerequisite - Initialize CouchbaseLite loading the native libraries
static {
System.out.println("Starting CouchbaseLite at " + LocalTime.now());
com.couchbase.lite.CouchbaseLite.init();
}- 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();- 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...
}
}
});- 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);
}
}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: 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: trueProperties 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.
TBD
TBD