Couchbae lite Sync and Update UI in Android

Hello,
I am working on Android Recipe App in which I have to implement couchbase for data sync,I am facing issue of smooth behavior and first data sync get lots of time and UI doesn’t reply as document sync. there are around 5000 document need to sync.

I create a singleton class where I am doing all couchbase related operation.

package com.breakfast.brunch.managers;

import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;

import com.breakfast.brunch.R;
import com.breakfast.brunch.models.Ingredian;
import com.breakfast.brunch.models.ShoppingModel;
import com.breakfast.brunch.pojo.ModelHelper;
import com.breakfast.brunch.utils.Constants;
import com.couchbase.lite.CouchbaseLiteException;
import com.couchbase.lite.Database;
import com.couchbase.lite.DatabaseOptions;
import com.couchbase.lite.Document;
import com.couchbase.lite.Emitter;
import com.couchbase.lite.LiveQuery;
import com.couchbase.lite.Manager;
import com.couchbase.lite.Mapper;
import com.couchbase.lite.QueryEnumerator;
import com.couchbase.lite.View;
import com.couchbase.lite.android.AndroidContext;
import com.couchbase.lite.auth.Authenticator;
import com.couchbase.lite.auth.PasswordAuthorizer;
import com.couchbase.lite.javascript.JavaScriptReplicationFilterCompiler;
import com.couchbase.lite.javascript.JavaScriptViewCompiler;
import com.couchbase.lite.replicator.Replication;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**

  • Created on 24/01/17.
    */

public class CBLSyncManager implements Replication.ChangeListener, Constants {
// Manager
// SharedManager
// Database of object

public static CBLSyncManager cblSyncManager;
public static Context mContext;
public Manager mManager;
public Database mDatabase;
public Database mReadableDatabase;
private String TAG = "#CBLSyncManager#";

public static CBLSyncManager getInstance(Context context) {
    mContext = context;
    if (cblSyncManager == null) {
        cblSyncManager = new CBLSyncManager();
    }
    return cblSyncManager;
}

private Manager getManager() {
    if (mManager == null) {
        try {
            AndroidContext context = new AndroidContext(mContext.getApplicationContext());
            mManager = new Manager(context, Manager.DEFAULT_OPTIONS);
            //install a view definition needed by the application
        } catch (Exception e) {
            com.couchbase.lite.util.Log.e(TAG, "Cannot create Manager object", e);
        }
    }
    return mManager;
}

public void setManager(Manager mManager) {
    this.mManager = mManager;
}

private void setLog(boolean enable) {
    if (enable) {
        Manager.enableLogging(TAG, com.couchbase.lite.util.Log.VERBOSE);
        Manager.enableLogging(com.couchbase.lite.util.Log.TAG, com.couchbase.lite.util.Log.VERBOSE);
        Manager.enableLogging(com.couchbase.lite.util.Log.TAG_SYNC_ASYNC_TASK, com.couchbase.lite.util.Log.VERBOSE);
        Manager.enableLogging(com.couchbase.lite.util.Log.TAG_SYNC, com.couchbase.lite.util.Log.VERBOSE);
        Manager.enableLogging(com.couchbase.lite.util.Log.TAG_QUERY, com.couchbase.lite.util.Log.VERBOSE);
        Manager.enableLogging(com.couchbase.lite.util.Log.TAG_VIEW, com.couchbase.lite.util.Log.VERBOSE);
        Manager.enableLogging(com.couchbase.lite.util.Log.TAG_DATABASE, com.couchbase.lite.util.Log.VERBOSE);
    }
}

public void startSync() {
    Authenticator auth1 = new PasswordAuthorizer("xxxxxx", "xxxxxxxxxxxx");

    View.setCompiler(new JavaScriptViewCompiler());
    Database.setFilterCompiler(new JavaScriptReplicationFilterCompiler());

// LiteListener liteListener = new LiteListener(getManager(), 5984, auth1);
// Thread thread = new Thread(liteListener);
// thread.start();
URL syncUrl;
try {
syncUrl = new URL(mContext.getString(R.string.syncUrl));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}

    Replication pullReplication = null;
    try {
        pullReplication = getDatabase().createPullReplication(syncUrl);
        pullReplication.setAuthenticator(auth1);
        pullReplication.setContinuous(true);

        List<String> channel = new ArrayList<>();
        channel.add("BreakFast");
        pullReplication.setChannels(channel);
        pullReplication.setFilter("sync_gateway/bychannel");
        pullReplication.start();

        pullReplication.addChangeListener(this);
    } catch (CouchbaseLiteException e) {
        e.printStackTrace();
    }

}

public Database getDatabase() throws CouchbaseLiteException {
    String key = "password123456";
    DatabaseOptions options = new DatabaseOptions();
    options.setCreate(true);
    options.setEncryptionKey(key);
    mDatabase = getManager().openDatabase(DATABASE_NAME, options);
    options.setStorageType(Manager.FORESTDB_STORAGE);
    return mDatabase;
}

public Database getReadDatabase() throws CouchbaseLiteException {
    String key = "password123456";
    DatabaseOptions options = new DatabaseOptions();
    options.setCreate(true);
    options.setEncryptionKey(key);
    mReadableDatabase = getManager().openDatabase(DATABASE_NAME, options);
    options.setStorageType(Manager.FORESTDB_STORAGE);
    return mReadableDatabase;
}

public void initFavorite(ArrayList<String> favRecipeIds) {
    Map<String, Object> properties = new HashMap<>();
    try {
        Document document = getDatabase().getDocument(FVTRCPS);
        if (getDatabase().getDocument(FVTRCPS).getProperties() == null) {
            if (favRecipeIds != null) {
                properties.put(RECIPEIDS, favRecipeIds);
                document.putProperties(properties);
            }
        } else {
            ArrayList<String> newIds = favRecipeIds;
            properties.put(RECIPEIDS, newIds);
            Map<String, Object> updatedProperties = new HashMap<String, Object>();
            updatedProperties.putAll(document.getProperties());
            updatedProperties.put(RECIPEIDS, newIds);
            document.putProperties(updatedProperties);
            Log.e(TAG, "Revision Doc: " + document.getProperties());
        }
    } catch (CouchbaseLiteException e) {
        e.printStackTrace();
    }
}

/**
 * @param model
 */
public void addItemToShoppingList(ShoppingModel model) {
    final String SHP_ID = SHP + model.getIngredientDocId().replace("INGR", "");
    try {

// Document document = getDatabase().getDocument(SHP_ID);
ModelHelper.save(getDatabase(), model, SHP_ID);
} catch (CouchbaseLiteException e) {
e.printStackTrace();
}
}

public void initUpdateShopping(ArrayList<Ingredian> custShopIngredientList) {
    Map<String, Object> properties = new HashMap<>();
    try {
        Document document = getDatabase().getDocument(SHPCUST);
        if (getDatabase().getDocument(SHPCUST).getProperties() == null) {
            //do nothing
        } else {
            ArrayList<Ingredian> newIds = custShopIngredientList;
            properties.put(INGREDIENS, newIds);
            Map<String, Object> updatedProperties = new HashMap<String, Object>();
            updatedProperties.putAll(document.getProperties());
            updatedProperties.put(INGREDIENS, newIds);
            document.putProperties(updatedProperties);
            Log.e(TAG, "Revision Doc: " + document.getProperties());
        }
    } catch (CouchbaseLiteException e) {
        e.printStackTrace();
    }
}

public void updateShoppingList(ShoppingModel shoppingModel) {

// final String SHP_ID = SHP + shoppingModel.get();
Map<String, Object> properties = new HashMap<>();
try {
Document document = getDatabase().getDocument(“shp-custom”);
Log.e(TAG, "updateShoppingList: " + document.getProperties());
properties.putAll(document.getProperties());
ObjectMapper m = new ObjectMapper();
Map<String, Object> props = m.convertValue(shoppingModel, Map.class);
try {
document.putProperties(props);
} catch (CouchbaseLiteException e) {
e.printStackTrace();
}
Log.e(TAG, "AFTER : " + document.getProperties());
} catch (CouchbaseLiteException e) {
e.printStackTrace();
}
}

public boolean hasCustomShoppingList(String documentId) {
    boolean result = false;

    try {
        Document document = getDatabase().getDocument(documentId);
        if (document.getProperty("_id") == null) {
            result = false;
        } else {
            result = true;
        }
    } catch (CouchbaseLiteException e) {
        e.printStackTrace();
        return result;
    }
    return result;
}

public ArrayList<String> getFavouriteRecipeIds() {
    try {
        Document document = getDatabase().getDocument(FVTRCPS);
        ArrayList<String> strings = (ArrayList<String>) document.getProperty(RECIPEIDS);
        Log.e(TAG, "initUI: " + strings);
        return strings;
    } catch (CouchbaseLiteException e) {
        e.printStackTrace();
        return null;
    }


}

@Deprecated
public void deleteShoppingList() {
    try {
        Document document = getDatabase().getDocument(SHPCUST);
        document.delete();
        Log.e(TAG, "deleteShoppingList: DONE");
    } catch (CouchbaseLiteException e) {
        Log.e(TAG, "deleteShoppingList: ERROR");
        e.printStackTrace();
    }
}

public View getDeleteShoppingView() {
    View view = CBLSyncManager.getInstance(mContext).mDatabase.getView(VIEW_DELETELIST);
    if (view.getMap() == null) {
        Mapper mapper = new Mapper() {
            public void map(Map<String, Object> document, Emitter emitter) {
                if (document.get(_ID).toString().contains(SHP)) {
                    String docID = (String) document.get(ID);
                    String[] temp = new String[]{docID};
                    emitter.emit(temp, null);
                }
            }
        };
        view.setMap(mapper, "1.0");
    }
    return view;
}

QueryEnumerator enumerator;

public void deleteShoppingList(int val) {
    LiveQuery mQuery = getDeleteShoppingView().createQuery().toLiveQuery();
    mQuery.addChangeListener(new LiveQuery.ChangeListener() {
        @Override
        public void changed(final LiveQuery.ChangeEvent event) {
            enumerator = event.getRows();
            for (int i = 0; i < event.getRows().getCount(); i++) {
                Document isShopDocuemnt = event.getRows().getRow(i).getDocument();
                try {
                    isShopDocuemnt.delete();
                    Log.e(TAG, "DELETE SUCCESS");
                } catch (CouchbaseLiteException e) {
                    Log.e(TAG, "DELETE FAIL");
                    e.printStackTrace();
                }
            }
        }
    });
    mQuery.start();
}

public Document getItem(int position) {
    return enumerator != null ? enumerator.getRow(position).getDocument() : null;
}

@Override
public void changed(Replication.ChangeEvent event) {
    Replication replication = event.getSource();
    com.couchbase.lite.util.Log.d(TAG, "Replication : " + replication + " changed.");
    /*switch (replication.getStatus()) {
        case REPLICATION_ACTIVE:
            Log.e(TAG, "REPLICATION_ACTIVE");
            break;
        case REPLICATION_IDLE:
            Log.e(TAG, "REPLICATION_IDLE");
            break;
        case REPLICATION_OFFLINE:
            Log.e(TAG, "REPLICATION_OFFLINE");
            break;
        case REPLICATION_STOPPED:
            Log.e(TAG, "REPLICATION_STOPPED");
            break;
    }

    if (!replication.isRunning()) {
        String msg = String.format("Replicator %s not running", replication);

// com.couchbase.lite.util.Log.d(TAG, msg);
// Log.e(TAG, “NOT RUNUNING ALL DONE”);
} else {
int processed = replication.getCompletedChangesCount();
int total = replication.getChangesCount();
String msg = String.format(“Replicator processed %d / %d”, processed, total);
// com.couchbase.lite.util.Log.d(TAG, msg);
// Log.e(TAG, “Process " + processed + " / " + total);
if (processed == total) {
// Log.e(TAG, String.format(”*** %d = %d Done", processed, total));
}
if (replication.getStatus() != Replication.ReplicationStatus.REPLICATION_ACTIVE) {
// Log.e(TAG, “changed: IN PROCESS”);
} else {
// Log.e(TAG, “ALL DONE”);
}
}

    if (event.getError() != null) {

// showError(“Sync error”, event.getError());
}*/
}
}

I call sync manager in Intent Service from Splash screen. in Home screen I have to display data in ViewPager and below that I have recyclerview for display data. but it’s not update as per document sync.

private void getChangedDocumentData() {
try {
mainDoc = CBLSyncManager.getInstance(mActivity).getDatabase().getDocument(RCPT_BREAKFAST);
mainDoc.addChangeListener(this);
setFeatureRecipeAndCategoryData();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(mActivity, "E " + e.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, “CouchbaseLiteException”);
}
}

What I understand from your requirement is either you want to sync some specified document first for that you can use filtered replication see following URL

https://developer.couchbase.com/documentation/mobile/1.4/guides/couchbase-lite/native-api/replication/index.html#filtered-replications

Filtered replication be based on channel or document id. The link above will help you understand it.

Else if you want to get all the documents at once on start of app and you want to identify the percentage of documents synced , You need to use one shot replication.

One shot replication can be used by passing false in setting continuous replication . Then if the replication status is Stopped , it means either all the documents have synced or there was some error. So if the Status is stopped and last error got nothing , it means one shot done. At that time you can put replication in continuous way so that future data changes will sync automatically.

I think this will help you.

Thanks for your explanation and answer.