Prerequesites:
- two Android devices running the same Android app and are signed in to the same account (optionally?) and use continuous push and pull sync. Omitted is account creation, creating sessions, initializing push and pull sync, etc.
- following SG fn is used
function sync_gateway_fn(doc, oldDoc) {
function printDoc(doc) {
for (var key in doc) {
if (doc.hasOwnProperty(key)) {
console.log(new Date() + ": printDoc = " + key + " -> " + doc[key]);
}
}
}
console.log(new Date() + ": start of SG fn");
if (doc) {
console.log(new Date() + ": print doc");
printDoc(doc);
}
if (oldDoc) {
console.log(new Date() + ": print old doc");
printDoc(oldDoc);
}
if (doc.type === 'test-type') {
console.log(new Date() + ": in if-clause");
if (oldDoc && !oldDoc._deleted) {
if (doc.age == null) {
console.log(new Date() + ": wrong value clause 1 and an exception is thrown");
throw ({
forbidden: doc
});
}
requireAccess(oldDoc.channels);
channel(doc.channels);
} else if (oldDoc && oldDoc._deleted) {
if (doc.age == null) {
console.log(new Date() + ": wrong value clause 2 and an exception is thrown");
throw ({
forbidden: doc
});
}
channel(doc.channels);
} else if (!oldDoc) {
if (doc.age == null) {
console.log(new Date() + ": wrong value clause 3 and an exception is thrown");
throw ({
forbidden: doc
});
}
channel(doc.channels);
}
} else {
console.log(new Date() + ": in else-clause");
if (oldDoc) {
console.log(new Date() + ": in else-clause ... if (oldDoc)");
requireAccess(oldDoc.channels);
}
channel(doc.channels)
}
}
Activity
public class ActTestType extends AppCompatActivity {
private final String MY_DOCUMENT_NAME = "my_doc_test_type_2";
private final String DEBUG = "DEBUG";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_test_type);
((TextView) findViewById(R.id.act_test_type_tv_1)).setText("Document name is " + MY_DOCUMENT_NAME);
findViewById(R.id.act_test_type_btn_create).setOnClickListener(view -> createTestTypeDocumentIfNotExists());
findViewById(R.id.act_test_type_btn_delete).setOnClickListener(view -> {
try {
boolean isDeleted = CouchbaseUtil.getInstance(ActTestType.this).getDatabase().getDocument(MY_DOCUMENT_NAME).delete();
Log.d(DEBUG, "Document is deleted = " + isDeleted);
} catch (CouchbaseLiteException e) {
e.printStackTrace();
}
});
findViewById(R.id.act_test_type_btn_1).setOnClickListener(view -> {
Document document = CouchbaseUtil.getInstance(ActTestType.this).getDatabase().getDocument(MY_DOCUMENT_NAME);
try {
document.update(newRevision -> {
Map<String, Object> properties = newRevision.getUserProperties();
properties.put("age", "twenty");
properties.put("type", "test-type");
properties.put("channels", SingletonApp.getInstance().getUuid());
newRevision.setUserProperties(properties);
return true;
});
} catch (CouchbaseLiteException e) {
e.printStackTrace();
}
});
findViewById(R.id.act_test_type_btn_2).setOnClickListener(view -> {
Document document = CouchbaseUtil.getInstance(ActTestType.this).getDatabase().getDocument(MY_DOCUMENT_NAME);
try {
document.update(newRevision -> {
Map<String, Object> properties = newRevision.getUserProperties();
properties.put("age", "thirty");
properties.put("type", "test-type");
properties.put("channels", SingletonApp.getInstance().getUuid());
newRevision.setUserProperties(properties);
return true;
});
} catch (CouchbaseLiteException e) {
e.printStackTrace();
}
});
findViewById(R.id.act_test_type_btn_3).setOnClickListener(view -> {
Document document = CouchbaseUtil.getInstance(ActTestType.this).getDatabase().getDocument(MY_DOCUMENT_NAME);
try {
document.update(newRevision -> {
Map<String, Object> properties = newRevision.getUserProperties();
properties.put("age", null);
properties.put("type", "test-type");
properties.put("channels", SingletonApp.getInstance().getUuid());
newRevision.setUserProperties(properties);
return true;
});
} catch (CouchbaseLiteException e) {
e.printStackTrace();
}
});
listenForChanges();
}
private void createTestTypeDocumentIfNotExists() {
Document document = CouchbaseUtil.getInstance(this).getDatabase().getExistingDocument(MY_DOCUMENT_NAME);
if (document == null) {
document = CouchbaseUtil.getInstance(this).getDatabase().getDocument(MY_DOCUMENT_NAME);
try {
document.update(newRevision -> {
Map<String, Object> properties = newRevision.getUserProperties();
properties.put("age", 0);
properties.put("type", "test-type");
properties.put("channels", SingletonApp.getInstance().getUuid());
newRevision.setUserProperties(properties);
return true;
});
} catch (CouchbaseLiteException e) {
e.printStackTrace();
}
}
}
private void listenForChanges() {
Document document = CouchbaseUtil.getInstance(this).getDatabase().getExistingDocument(MY_DOCUMENT_NAME);
if (document != null) {
document.addChangeListener(event -> {
updateUiText();
});
}
}
private void updateUiText() {
PojoTestType pojoTestType = UtilObjectMapper.getMapper()
.convertValue(CouchbaseUtil.getInstance(this).getDatabase().getExistingDocument(MY_DOCUMENT_NAME).getProperties(), PojoTestType.class);
Log.d(DEBUG, "pojo = " + pojoTestType.toString());
runOnUiThread(() -> ((TextView) findViewById(R.id.act_test_type_tv_2)).setText("Saved age = " + pojoTestType.age));
}
}
POJO
public class PojoTestType {
String age;
public PojoTestType() {
}
@Override
public String toString() {
return "PojoTestType{" + "age=" + age + '}';
}
}
Layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">
<TextView
android:id="@+id/act_test_type_tv_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/act_test_type_tv_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Saved age = "
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/act_test_type_tv_1"/>
<Button
android:id="@+id/act_test_type_btn_create"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Create document"
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/act_test_type_tv_2"/>
<Button
android:id="@+id/act_test_type_btn_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Set age to twenty"
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/act_test_type_btn_create"/>
<Button
android:id="@+id/act_test_type_btn_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Set age to thirty"
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/act_test_type_btn_1"/>
<Button
android:id="@+id/act_test_type_btn_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Set age to null"
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/act_test_type_btn_2"/>
<Button
android:id="@+id/act_test_type_btn_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Delete document"
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/act_test_type_btn_3"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Steps to reproduce and SG fn logs. Scroll horizontally to see steps if necessary
Thu, 08 Aug 2019 09:25:45 CEST: start of SG fn <-- Create doc on device A, age is set to 0.
Thu, 08 Aug 2019 09:25:45 CEST: print doc
Thu, 08 Aug 2019 09:25:45 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:25:45 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:25:45 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:25:45 CEST: printDoc = age -> 0
Thu, 08 Aug 2019 09:25:45 CEST: printDoc = _rev -> 1-3fe9c953404a8d0e7177b81f327ac52f
Thu, 08 Aug 2019 09:25:45 CEST: printDoc = _revisions -> [object Object]
Thu, 08 Aug 2019 09:25:45 CEST: in if-clause
Thu, 08 Aug 2019 09:26:09 CEST: start of SG fn <-- Update doc on device A, age is set twenty.
Thu, 08 Aug 2019 09:26:09 CEST: print doc
Thu, 08 Aug 2019 09:26:09 CEST: printDoc = _rev -> 2-f51836ef232d27a2a99b2f815562981b
Thu, 08 Aug 2019 09:26:09 CEST: printDoc = _revisions -> [object Object]
Thu, 08 Aug 2019 09:26:09 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:09 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:09 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:09 CEST: printDoc = age -> twenty
Thu, 08 Aug 2019 09:26:09 CEST: print old doc
Thu, 08 Aug 2019 09:26:09 CEST: printDoc = age -> 0
Thu, 08 Aug 2019 09:26:09 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:09 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:09 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:09 CEST: in if-clause
Thu, 08 Aug 2019 09:26:20 CEST: start of SG fn <-- Update doc on device A, age is set thirty.
Thu, 08 Aug 2019 09:26:20 CEST: print doc
Thu, 08 Aug 2019 09:26:20 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:20 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:20 CEST: printDoc = age -> thirty
Thu, 08 Aug 2019 09:26:20 CEST: printDoc = _rev -> 3-9f57d24e52c63880118e78cc1755b6c5
Thu, 08 Aug 2019 09:26:20 CEST: printDoc = _revisions -> [object Object]
Thu, 08 Aug 2019 09:26:20 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:20 CEST: print old doc
Thu, 08 Aug 2019 09:26:20 CEST: printDoc = age -> twenty
Thu, 08 Aug 2019 09:26:20 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:20 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:20 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:20 CEST: in if-clause
Thu, 08 Aug 2019 09:26:28 CEST: start of SG fn <-- Update doc on device B, age is set to null.
Thu, 08 Aug 2019 09:26:28 CEST: print doc
Thu, 08 Aug 2019 09:26:28 CEST: printDoc = _rev -> 4-925f4c03c89adb3c6fe07c0a1fd3873c
Thu, 08 Aug 2019 09:26:28 CEST: printDoc = _revisions -> [object Object]
Thu, 08 Aug 2019 09:26:28 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:28 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:28 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:28 CEST: printDoc = age -> undefined
Thu, 08 Aug 2019 09:26:28 CEST: print old doc
Thu, 08 Aug 2019 09:26:28 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:28 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:28 CEST: printDoc = age -> thirty
Thu, 08 Aug 2019 09:26:28 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:28 CEST: in if-clause
Thu, 08 Aug 2019 09:26:28 CEST: wrong value clause 1 and an exception is thrown <-- This is expected. Reject invalid values at the SG fn layer.
Thu, 08 Aug 2019 09:26:37 CEST: start of SG fn <-- Update doc on device A, age is set to twenty.
Thu, 08 Aug 2019 09:26:37 CEST: print doc
Thu, 08 Aug 2019 09:26:37 CEST: printDoc = _rev -> 4-42384f8927dd7f363e228979e422ca31
Thu, 08 Aug 2019 09:26:37 CEST: printDoc = _revisions -> [object Object]
Thu, 08 Aug 2019 09:26:37 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:37 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:37 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:37 CEST: printDoc = age -> twenty
Thu, 08 Aug 2019 09:26:37 CEST: print old doc
Thu, 08 Aug 2019 09:26:37 CEST: printDoc = age -> thirty
Thu, 08 Aug 2019 09:26:37 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:37 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:37 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:37 CEST: in if-clause
Thu, 08 Aug 2019 09:26:38 CEST: start of SG fn <-- Unsure what starts this. Afterwards the document is not found on the server anymore, i.e. in the documents view in the Couchbase server web console.
Thu, 08 Aug 2019 09:26:38 CEST: print doc
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = _rev -> 5-4459d6b8f7882cd66443049e03ac41f6
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = _revisions -> [object Object]
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = _deleted -> true
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:38 CEST: print old doc
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = age -> twenty
Thu, 08 Aug 2019 09:26:38 CEST: in else-clause <-- First time in else-clause.
Thu, 08 Aug 2019 09:26:38 CEST: in else-clause ... if (oldDoc)
Thu, 08 Aug 2019 09:26:38 CEST: start of SG fn <-- Unsure who starts this.
Thu, 08 Aug 2019 09:26:38 CEST: print doc
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = _revisions -> [object Object]
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = age -> undefined
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = _rev -> 5-05278c8e912a58d2a121b6db51a06d31
Thu, 08 Aug 2019 09:26:38 CEST: print old doc
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = age -> thirty
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = channels -> AOGsnkwf8BR8AwKb0BH9r4JXi5P2
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = type -> test-type
Thu, 08 Aug 2019 09:26:38 CEST: printDoc = _id -> my_doc_test_type_2
Thu, 08 Aug 2019 09:26:38 CEST: in if-clause
Thu, 08 Aug 2019 09:26:38 CEST: wrong value clause 1 and an exception is thrown
The document is deleted on Couchbase Server and on device A. Device B still has the document.