diff --git a/dependencies.list b/dependencies.list
index 4c90a89d30..a3fd56b4ef 100644
--- a/dependencies.list
+++ b/dependencies.list
@@ -7,10 +7,10 @@ REALM_CORE=10.3.3
MONGODB_REALM_SERVER=2021-03-09
# Common Android settings across projects
-GRADLE_BUILD_TOOLS=4.0.0
+GRADLE_BUILD_TOOLS=4.1.0
ANDROID_BUILD_TOOLS=29.0.3
-KOTLIN=1.3.72
-KOTLIN_COROUTINES=1.3.9
+KOTLIN=1.4.31
+KOTLIN_COROUTINES=1.4.2
# Common classpath dependencies
gradle=6.5
diff --git a/examples/mongoDbRealmExample/build.gradle b/examples/mongoDbRealmExample/build.gradle
index 8bba9f531e..a1f05aadcb 100644
--- a/examples/mongoDbRealmExample/build.gradle
+++ b/examples/mongoDbRealmExample/build.gradle
@@ -34,13 +34,10 @@ android {
buildTypes {
// Configure server and App Id.
- // The default server is https://realm-dev.mongodb.com/ . Go to that and copy the MongoDB
+ // The default server is https://realm.mongodb.com/ . Go to that and copy the MongoDB
// Realm App Id.
- //
- // If you are running a local version of MongoDB Realm, modify endpoint accordingly. Most
- // likely it is "http://localhost:9090"
- def mongodbRealmUrl = "https://realm-dev.mongodb.com"
- def appId = "my-app-id"
+ def mongodbRealmUrl = "https://realm.mongodb.com"
+ def appId = "counter-app-snuns"
debug {
buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\""
buildConfigField "String", "MONGODB_REALM_APP_ID", "\"${appId}\""
@@ -65,8 +62,9 @@ realm {
}
dependencies {
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'com.google.android.material:material:1.1.0'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel:2.3.0'
+ implementation 'com.google.android.material:material:1.3.0'
implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
diff --git a/examples/mongoDbRealmExample/src/main/AndroidManifest.xml b/examples/mongoDbRealmExample/src/main/AndroidManifest.xml
index eb8829a1c6..51e2ef01c2 100644
--- a/examples/mongoDbRealmExample/src/main/AndroidManifest.xml
+++ b/examples/mongoDbRealmExample/src/main/AndroidManifest.xml
@@ -18,7 +18,13 @@
+ android:label="Login">
+
+
+
diff --git a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/ClientResetActivity.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/ClientResetActivity.kt
new file mode 100644
index 0000000000..438e9c6821
--- /dev/null
+++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/ClientResetActivity.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.realm.example
+
+import android.os.Bundle
+import android.os.SystemClock
+import android.view.View
+import android.widget.TextView
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import com.mongodb.realm.example.databinding.ActivityClientresetBinding
+import io.realm.*
+import io.realm.mongodb.sync.*
+import me.zhanghai.android.materialprogressbar.MaterialProgressBar
+import java.util.*
+import java.util.concurrent.ConcurrentLinkedQueue
+
+/**
+ * This class is used as an example on how to implement Client Reset.
+ *
+ * This activity is launched with `singleInstance` so no matter how many times it is
+ * started only one instance is running. However, when finished, it will just return to whatever is
+ * on the top of navigation stack.
+ *
+ * Pressing back has been disabled to prevent the [clientResetHelper] from accidentally running
+ * while another Activity is displayed.
+ */
+class ClientResetActivity : AppCompatActivity() {
+
+ companion object {
+ // Track Client Reset errors as a queue of errors as the chance of all Realms
+ // connected to a single app instance experiencing Client Resets is quite high, e.g.
+ // in the case where Sync was terminated then restarted on the server.
+ var RESET_ERRORS = ConcurrentLinkedQueue>()
+ }
+
+ // Run Client Reset logic on a separate helper thread to make it easier to implement timeouts.
+ // Note, this Runnable is not particular safe as it will continue running even if the Activity
+ // is excited
+ private val clientResetHelper = Runnable {
+ var errorReported = false;
+ ClientResetLoop@ while(true) {
+ val error: Pair = RESET_ERRORS.poll() ?: break
+ val clientReset: ClientResetRequiredError = error.first
+ val config: SyncConfiguration = error.second
+
+ // The background Sync Client take about 10 seconds to fully close the connection and thus
+ // the background Realm. Set timeout to 20 seconds.
+ var maxWait = 20
+ while (Realm.getGlobalInstanceCount(config) > 0) {
+ if (maxWait == 0) {
+ runOnUiThread {
+ progressBar.visibility = View.INVISIBLE
+ statusView.text = "'${config.realmFileName}' did not fully close, so database could not be reset. Aborting"
+ }
+ errorReported = true
+ break@ClientResetLoop
+ } else {
+ maxWait--
+ runOnUiThread {
+ statusView.text = "Waiting for '${config.realmFileName}' to fully close ($maxWait): ${Realm.getGlobalInstanceCount(config)}"
+ }
+ SystemClock.sleep(1000)
+ }
+ }
+ clientReset.executeClientReset()
+ runOnUiThread {
+ statusView.text = ""
+ }
+ }
+ if (!errorReported) {
+ finish()
+ }
+ }
+
+ private lateinit var binding: ActivityClientresetBinding
+
+ private lateinit var statusView: TextView
+ private lateinit var progressBar: MaterialProgressBar
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_clientreset)
+ statusView = binding.status
+ progressBar = binding.progressbar
+ }
+
+ override fun onResume() {
+ super.onResume()
+ Thread(clientResetHelper).start();
+ }
+
+ override fun onBackPressed() {
+ Toast.makeText(
+ this,
+ "Pressing 'Back' is disabled while Client Reset is running.",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+}
diff --git a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt
index e5279180e3..95300deae4 100644
--- a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt
+++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt
@@ -32,10 +32,7 @@ import io.realm.kotlin.syncSession
import io.realm.kotlin.where
import io.realm.log.RealmLog
import io.realm.mongodb.User
-import io.realm.mongodb.sync.ProgressListener
-import io.realm.mongodb.sync.ProgressMode
-import io.realm.mongodb.sync.SyncConfiguration
-import io.realm.mongodb.sync.SyncSession
+import io.realm.mongodb.sync.*
import me.zhanghai.android.materialprogressbar.MaterialProgressBar
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
@@ -97,43 +94,37 @@ class CounterActivity : AppCompatActivity() {
user = loggedInUser
val user = user
if (user != null) {
- // Create a RealmConfiguration for our user
- // Use user id as partition value, so each user gets an unique view.
- // FIXME Right now we are using waitForInitialRemoteData and a more advanced
- // initialData block due to Sync only supporting ObjectId keys. This should
- // be changed once natural keys are supported.
+ // Create a RealmConfiguration for our user. Use user id as partition value, so each
+ // user gets an unique view.
val config = SyncConfiguration.Builder(user, user.id)
.initialData {
- if (it.isEmpty) {
- it.insert(CRDTCounter())
- }
+ it.insert(CRDTCounter(user.id))
+ }
+ .clientResetHandler { session, error ->
+ ClientResetActivity.RESET_ERRORS.add(Pair(error, session.configuration))
+ val intent = Intent(this, ClientResetActivity::class.java)
+ startActivity(intent)
}
- .waitForInitialRemoteData()
.build()
- // This will automatically sync all changes in the background for as long as the Realm is open
- Realm.getInstanceAsync(config, object: Realm.Callback() {
- override fun onSuccess(realm: Realm) {
- this@CounterActivity.realm = realm
-
- counter = realm.where().findFirstAsync()
- counter.addChangeListener { obj, _ ->
- if (obj.isValid) {
- counterView.text = String.format(Locale.US, "%d", counter.count)
- } else {
- counterView.text = "-"
- }
+ realm = Realm.getInstance(config).also {
+ counter = it.where().findFirstAsync()
+ counter.addChangeListener { obj, _ ->
+ if (obj.isValid) {
+ counterView.text = String.format(Locale.US, "%d", counter.count)
+ } else {
+ counterView.text = "-"
}
+ }
- // Setup progress listeners for indeterminate progress bars
- session = realm.syncSession
- session.run {
- addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener)
- addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener)
- }
+ // Setup progress listeners for indeterminate progress bars
+ session = it.syncSession
+ session.run {
+ addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener)
+ addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener)
}
- })
- counterView.text = "-"
+ counterView.text = "-"
+ }
}
}
@@ -144,6 +135,7 @@ class CounterActivity : AppCompatActivity() {
removeProgressListener(downloadListener)
removeProgressListener(uploadListener)
}
+ // Close Realm here to make sure it is closed when navigating to other Activities.
realm?.close()
}
}
@@ -156,15 +148,16 @@ class CounterActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_logout -> {
- val user = user
- user?.logOutAsync {
- if (it.isSuccess) {
- realm?.close()
- this.user = loggedInUser
- } else {
- RealmLog.error(it.error.toString())
- }
- }
+// val user = user
+// user?.logOutAsync {
+// if (it.isSuccess) {
+// realm?.close()
+// this.user = loggedInUser
+// } else {
+// RealmLog.error(it.error.toString())
+// }
+// }
+ ResetHelper.triggerClientReset(user!!.app.sync, realm!!.syncSession)
true
}
else -> super.onOptionsItemSelected(item)
diff --git a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/model/CRDTCounter.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/model/CRDTCounter.kt
index 54d9b97ad1..db967a46f0 100644
--- a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/model/CRDTCounter.kt
+++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/model/CRDTCounter.kt
@@ -22,11 +22,15 @@ import io.realm.annotations.RealmField
import io.realm.annotations.Required
import org.bson.types.ObjectId
-open class CRDTCounter : RealmObject() {
+open class CRDTCounter(userId: String) : RealmObject() {
+
+ // Required by Realm
+ constructor(): this("")
@PrimaryKey
@RealmField("_id")
- var id: ObjectId = ObjectId.get()
+ var id: String = userId
+
@Required
private val counter: MutableRealmInteger = MutableRealmInteger.valueOf(0L)
diff --git a/examples/mongoDbRealmExample/src/main/java/io/realm/mongodb/sync/ResetHelper.kt b/examples/mongoDbRealmExample/src/main/java/io/realm/mongodb/sync/ResetHelper.kt
new file mode 100644
index 0000000000..2f33c39220
--- /dev/null
+++ b/examples/mongoDbRealmExample/src/main/java/io/realm/mongodb/sync/ResetHelper.kt
@@ -0,0 +1,9 @@
+package io.realm.mongodb.sync
+
+class ResetHelper {
+ companion object {
+ fun triggerClientReset(sync: Sync, session: SyncSession) {
+ sync.simulateClientReset(session)
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/mongoDbRealmExample/src/main/res/layout/activity_clientreset.xml b/examples/mongoDbRealmExample/src/main/res/layout/activity_clientreset.xml
new file mode 100644
index 0000000000..a45ff28619
--- /dev/null
+++ b/examples/mongoDbRealmExample/src/main/res/layout/activity_clientreset.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+