profile
viewpoint
Realm realm https://realm.io Realm is a mobile database: a replacement for SQLite & ORMs. SDKs for Swift, Objective-C, Java, Kotlin, C#, and JavaScript.

realm/jazzy 7262

Soulful docs for Swift & Objective-C

realm/realm-browser-osx 498

DEPRECATED - Realm Browser for Mac OS X has been replaced by realm-studio which is cross platform.

realm/realm-android-adapters 414

Adapters for combining Realm Java with Android UI components and framework classes

realm/github-gantt 152

Generate Gantt Charts From Github Issues!

realm/EventKit 63

A template conference app, featuring real-time schedule and data changes & running on Realm 🚀

realm/react-realm-context 49

Components that simplifies using Realm with React

realm/my-first-realm-app 40

ToDo demo app using Realm and Realm Object Server to synchronize tasks.

realm/node-template-project 19

A template for your Node and TypeScript Project with Visual Studio Code Debugging!

PR opened realm/realm-dart

Use PartBuilder instead of SharedPartBuilder

This will allow other builders to consume output of the realm generator. Generated files are now suffixed with .realm.dart instead of .g.dart.

+290 -189

0 comment

72 changed files

pr created time in 16 minutes

create barnchrealm/realm-dart

branch : kn/part-builder

created branch time in 16 minutes

push eventrealm/realm-js

Ferdinando Papale

commit sha 0e4303b779e6e180e4b5325f116e1e681c76aa9f

Corrected changelog

view details

push time in 33 minutes

push eventrealm/realm-js

Ferdinando Papale

commit sha eba0df9187e8b9754037013ccf1ca96318ffd574

Updated changelog

view details

push time in 35 minutes

PR opened realm/realm-js

Update main to core v13.13.0
+6 -2

0 comment

3 changed files

pr created time in 36 minutes

push eventrealm/realm-js

Mathias Stearn

commit sha 525071c726f6a26ba0b5c59381e6dbc464389de5

Enable cleartext traffic in android test app to make tests work in release builds

view details

Kræn Hansen

commit sha 7a6a769265d515a00363ed1710e8cb8cec442102

Update package-unit-tests.yml to add ccache and ninja (#5837)

view details

Kræn Hansen

commit sha e3d63cdcccbc28a1d0ddb0fba7e9dc6df3fa565f

Update install-test-react-native.yml (#5848)

view details

Andrew Meyer

commit sha 24e875d3391fb7f1d91d54d15103abfee27539d7

Fix warning for deprecated namespace setting method in Android (#5862)

view details

LJ

commit sha 3a00265cff2edbd810ff97f8bdfd0a6603002617

Add Flexible Sync subscribe/unsubscribe APIs (#5772) * Implement 'subscribe()' w/o 'timeout' option. * Add initial 'subscribe()' tests. * Implement 'unsubscribe()'. * Add initial 'unsubscribe()' tests. * Refactor 'TimeoutPromise' to optionally not reject on timeout. * Update 'subscribe()' to handle 'timeout' option. * Update tests. * Update 'unsubscribe()' and let 'mutableSubs.remove()' handle the not-found case. * Replace 'Results' instance field with call to Core. * Add 'unnamedOnly' param to 'MutableSubscriptionSet.removeAll()'. * Add test for removing unnamed subscriptions. * Add CHANGELOG entry. * Update formatting. * Update TSDocs. * Add test for 'subscribe()'. * Update use of 'SubscriptionsState' to 'SubscriptionSetState'. * Remove 'unnamedOnly' param and create 'removeUnnamed()' method. * Treat a subscription with an empty name as named. (Same behavior as v11) * Mark 'Results.unsubscribe()' as experimental. * Remove boolean return type from 'Results.unsubscribe()'. * Update minor formatting in CHANGELOG. * Store subscription name on 'Results' to unsubscribe correctly. * Add more tests to 'unsubscribe()'. * Update minor formatting in test. * Add comment regarding 'this.timeout()' in test. * Mark 'subscribe()' as experimental. * Add clarification to test. * Change ordering of condition checks. * Update CHANGELOG.md Co-authored-by: Kræn Hansen <kraen.hansen@mongodb.com> --------- Co-authored-by: Kræn Hansen <kraen.hansen@mongodb.com>

view details

Nick Larew

commit sha ca790bedfb5d6d1ecd1f557ee951c0fa5175b142

Fix User.callFunction JSDoc to match the v11+ API (#5768)

view details

Ferdinando Papale

commit sha a755aeb70d681c375019685203294cdb0c9e381d

Merge branch 'main' into fp/update-core-13.13 * main: Fix User.callFunction JSDoc to match the v11+ API (#5768) Add Flexible Sync subscribe/unsubscribe APIs (#5772) Fix warning for deprecated namespace setting method in Android (#5862) Update install-test-react-native.yml (#5848) Update package-unit-tests.yml to add ccache and ninja (#5837) Enable cleartext traffic in android test app to make tests work in release builds # Conflicts: # CHANGELOG.md

view details

push time in 38 minutes

push eventrealm/realm-core

Ferdinando Papale

commit sha fa33cc9d4d56e5a27fc0e9483a4aa6f281d0a76e

Fixed name

view details

push time in 42 minutes

push eventrealm/realm-core

Ferdinando Papale

commit sha 6a7ce4c0dcd56cef7cfb93a1ee7ee68db4c30d71

Update core 13.13 in bindgen branch (#6683) Co-authored-by: Christian Melchior <christian.melchior@mongodb.com> Co-authored-by: James Stone <james.stone@mongodb.com> Co-authored-by: realm-ci <ci@realm.io> Co-authored-by: Kirill Burtsev <kirill.burtsev@mongodb.com> Co-authored-by: Daniel Tabacaru <96778637+danieltabacaru@users.noreply.github.com> Co-authored-by: Thomas Goyne <tg@realm.io> Co-authored-by: Thomas Goyne <thomas.goyne@mongodb.com> Co-authored-by: Jørgen Edelbo <jorgen.edelbo@mongodb.com> Co-authored-by: Michael Wilkerson-Barker <michael.wilkersonbarker@mongodb.com> Co-authored-by: Nicola Cabiddu <nicola.cabiddu@mongodb.com> fix entries that went to the wrong change version (#6632) fix a race in a test (#6651) Fix a lock order inversion in tests (#6666) Fix an assertion failure if an async write callback ran during a write transaction (#6661)

view details

Ferdinando Papale

commit sha 6d0d6e92e51d558b31392148ca1506d3004a306b

Merge branch 'bindgen' into fp/geospatial * bindgen: Update core 13.13 in bindgen branch (#6683)

view details

push time in an hour

push eventrealm/realm-js

Ferdinando Papale

commit sha e3be0ce15a0c010714e5b123feb2ac956f27668e

Imrpoved queries

view details

push time in an hour

PR opened realm/realm-kotlin

Fix async server errors resulting in a native crash.

Fixes part of #1401 (still investigating the persisted name issue)

+77 -40

0 comment

6 changed files

pr created time in an hour

push eventrealm/realm-kotlin

Christian Melchior

commit sha 55a2ce85a623fbe3773b63f607ed4a4dc528b346

Cleanup

view details

push time in an hour

push eventrealm/realm-js

Andrew Meyer

commit sha bc600da7caa016ea4005f8883b4b4e7a1d7abdc5

Share the auth operation state between both useAuth and useEmailPasswordAuth

view details

push time in an hour

PullRequestReviewEvent

delete branch realm/realm-core

delete branch : fp/update-core-13.13

delete time in an hour

PR merged realm/realm-core

Reviewers
Update core 13.13 in bindgen branch
+3296 -2112

3 comments

90 changed files

papafe

pr closed time in an hour

push eventrealm/realm-core

Ferdinando Papale

commit sha 6a7ce4c0dcd56cef7cfb93a1ee7ee68db4c30d71

Update core 13.13 in bindgen branch (#6683) Co-authored-by: Christian Melchior <christian.melchior@mongodb.com> Co-authored-by: James Stone <james.stone@mongodb.com> Co-authored-by: realm-ci <ci@realm.io> Co-authored-by: Kirill Burtsev <kirill.burtsev@mongodb.com> Co-authored-by: Daniel Tabacaru <96778637+danieltabacaru@users.noreply.github.com> Co-authored-by: Thomas Goyne <tg@realm.io> Co-authored-by: Thomas Goyne <thomas.goyne@mongodb.com> Co-authored-by: Jørgen Edelbo <jorgen.edelbo@mongodb.com> Co-authored-by: Michael Wilkerson-Barker <michael.wilkersonbarker@mongodb.com> Co-authored-by: Nicola Cabiddu <nicola.cabiddu@mongodb.com> fix entries that went to the wrong change version (#6632) fix a race in a test (#6651) Fix a lock order inversion in tests (#6666) Fix an assertion failure if an async write callback ran during a write transaction (#6661)

view details

push time in an hour

push eventrealm/realm-kotlin

Christian Melchior

commit sha 0fda36d00ad4a27a8ac0eebfa67104dbe72a9336

Update changelog

view details

push time in an hour

push eventrealm/realm-js

Andrew Meyer

commit sha b6430da17e69b548e29f9103b2a0773f28e714da

Share the auth operation state between both useAuth and useEmailPasswordAuth

view details

push time in an hour

create barnchrealm/realm-kotlin

branch : cm/fix-async-server-error-crash

created branch time in an hour

Pull request review commentrealm/realm-kotlin

Improved subscribe API

+package io.realm.kotlin.test.mongodb.shared++import io.realm.kotlin.Realm+import io.realm.kotlin.entities.sync.flx.FlexChildObject+import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject+import io.realm.kotlin.entities.sync.flx.FlexParentObject+import io.realm.kotlin.ext.query+import io.realm.kotlin.internal.platform.runBlocking+import io.realm.kotlin.mongodb.ext.subscribe+import io.realm.kotlin.mongodb.subscriptions+import io.realm.kotlin.mongodb.sync.Subscription+import io.realm.kotlin.mongodb.sync.SubscriptionSetState+import io.realm.kotlin.mongodb.sync.SyncConfiguration+import io.realm.kotlin.mongodb.sync.WaitForSync+import io.realm.kotlin.mongodb.syncSession+import io.realm.kotlin.query.RealmResults+import io.realm.kotlin.test.mongodb.TEST_APP_FLEX+import io.realm.kotlin.test.mongodb.TestApp+import io.realm.kotlin.test.mongodb.createUserAndLogIn+import io.realm.kotlin.test.util.TestHelper+import io.realm.kotlin.test.util.use+import kotlinx.coroutines.TimeoutCancellationException+import kotlin.random.Random+import kotlin.test.AfterTest+import kotlin.test.BeforeTest+import kotlin.test.Test+import kotlin.test.assertEquals+import kotlin.test.assertFailsWith+import kotlin.test.assertNotEquals+import kotlin.test.assertNull+import kotlin.time.Duration.Companion.nanoseconds+import kotlin.time.Duration.Companion.seconds++/**+ * Class for testing the various extension methods we have for bridging the gap between Subscriptions+ * and RealmQuery/RealmResults.+ */+class SubscriptionExtensionsTests {++    private lateinit var app: TestApp+    private lateinit var realm: Realm++    @BeforeTest+    fun setup() {+        app = TestApp(appName = TEST_APP_FLEX)+        val (email, password) = TestHelper.randomEmail() to "password1234"+        val user = runBlocking {+            app.createUserAndLogIn(email, password)+        }+        val config = SyncConfiguration.Builder(+            user,+            schema = setOf(FlexParentObject::class, FlexChildObject::class, FlexEmbeddedObject::class)+        )+            .build()+        realm = Realm.open(config)+    }++    @AfterTest+    fun tearDown() {+        if (this::realm.isInitialized && !realm.isClosed()) {+            realm.close()+        }+        if (this::app.isInitialized) {+            app.close()+        }+    }++    @Test+    fun realmQuery_subscribe_anonymous() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        val results: RealmResults<FlexParentObject> = realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that subscribing twice to a query will result in the same subscription+    @Test+    fun realmQuery_subscribe_anonymousTwice() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that anonymous RealmQuery and RealmResults .subscribe calls result in the same sub.+    @Test+    fun anonymousSubscriptionsOverlap() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().find().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Verify that the realm query doesn't run against a frozen version previous to the Realm
    // Verify that the realm query doesn't run against a frozen version prior to the Realm
cmelchior

comment created time in an hour

Pull request review commentrealm/realm-kotlin

Improved subscribe API

+@file:Suppress("invisible_reference", "invisible_member")+package io.realm.kotlin.mongodb.ext++import io.realm.kotlin.mongodb.annotations.ExperimentalFlexibleSyncApi+import io.realm.kotlin.mongodb.sync.Subscription+import io.realm.kotlin.mongodb.sync.SubscriptionSet+import io.realm.kotlin.mongodb.sync.WaitForSync+import io.realm.kotlin.query.RealmQuery+import io.realm.kotlin.query.RealmResults+import io.realm.kotlin.types.RealmObject+import kotlin.time.Duration++/**+ * Automatically create an anonymous [Subscription] from a local query result in the background and+ * return the result of re-running the same query against the Realm file.+ *+ * This is a more streamlined alternative to doing something like this:+ *+ * ```+ * fun suspend getData(realm: Realm): RealmResults<Person> {+ *     val results = realm.query<Person>().find()+ *     realm.subscriptions.update { bgRealm ->+ *         add("myquery", results.query(""))+ *     }+ *     realm.subscriptions.waitForSynchronization()+ *     return realm.query<Person>().find()+ * }+ * ```+ *+ * It is possible to define whether or not to wait for the server to send all data before+ * running the local query. This is relevant as there might be delay from creating a subscription+ * to the data being available on the device due to either latency or because a large dataset needs+ * be downloaded.+ *+ * The default behaviour is that the first time `subscribe` is called, the query result will not+ * be returned until data has been downloaded from the server. On subsequent calls to `subscribe`+ * for the same query, the query will run immediately on the local database while any updates+ * are downloaded in the background.+ *+ * @param name name of the subscription. This can be used to identify it later in the [SubscriptionSet].+ * @param mode type of mode used to resolve the subscription. See [WaitForSync] for more details.+ * @param timeout How long to wait for the server to return the objects defined by the subscription.+ * This is only relevant for [WaitForSync.ALWAYS] and [WaitForSync.FIRST_TIME].+ * @return The result of running the query against the local Realm file. The results returned will+ * depend on which [mode] was used.+ * @throws kotlinx.coroutines.TimeoutCancellationException if the specified timeout was hit before+ * a query result could be returned.+ * @Throws IllegalStateException if this method is called on a Realm that isn't using Flexible Sync.+ */@ExperimentalFlexibleSyncApi+public suspend fun <T : RealmObject> RealmResults<T>.subscribe(+    mode: WaitForSync = WaitForSync.FIRST_TIME,+    timeout: Duration = Duration.INFINITE+): RealmResults<T> {+    val query: RealmQuery<T> = this.query("")+    return query.subscribe(mode, timeout)+}++/**+ * Automatically create a named [Subscription] from a local query result in the background and+ * return the result of re-running the same query against the Realm file.+ *+ * This is a more streamlined alternative to doing something like this:+ *+ * ```+ * fun suspend getData(realm: Realm): RealmResults<Person> {+ *     val results = realm.query<Person>().find()+ *     realm.subscriptions.update { bgRealm ->+ *         add("myquery", results.query(""))+ *     }+ *     realm.subscriptions.waitForSynchronization()+ *     return realm.query<Person>().find()+ * }+ * ```+ *+ * It is possible to define whether or not to wait for the server to send all data before+ * running the local query. This is relevant as there might be delay from creating a subscription+ * to the data being available on the device due to either latency or because a large dataset needs+ * be downloaded.+ *+ * The default behaviour is that the first time `subscribe` is called, the query result will not+ * be returned until data has been downloaded from the server. On subsequent calls to `subscribe`+ * for the same query, the query will run immediately on the local database while any updates+ * are downloaded in the background.+ *+ * @param name name of the subscription. This can be used to identify it later in the [SubscriptionSet].+ * @param mode type of mode used to resolve the subscription. See [WaitForSync] for more details.+ * @param timeout How long to wait for the server to return the objects defined by the subscription.+ * This is only relevant for [WaitForSync.ALWAYS] and [WaitForSync.FIRST_TIME].+ * @return The result of running the query against the local Realm file. The results returned will+ * depend on which [mode] was used.+ * @throws kotlinx.coroutines.TimeoutCancellationException if the specified timeout was hit before+ * a query result could be returned.+ * @Throws IllegalStateException if this method is called on a Realm that isn't using Flexible Sync.+ */+@ExperimentalFlexibleSyncApi+public suspend fun <T : RealmObject> RealmResults<T>.subscribe(+    name: String,

Would it make sense to collapse the anonymous and named variant by allowing name: String? = null to reduce the API surface 🤔

cmelchior

comment created time in an hour

Pull request review commentrealm/realm-kotlin

Improved subscribe API

+@file:Suppress("invisible_reference", "invisible_member")+package io.realm.kotlin.mongodb.ext++import io.realm.kotlin.mongodb.annotations.ExperimentalFlexibleSyncApi+import io.realm.kotlin.mongodb.sync.Subscription+import io.realm.kotlin.mongodb.sync.SubscriptionSet+import io.realm.kotlin.mongodb.sync.WaitForSync+import io.realm.kotlin.query.RealmQuery+import io.realm.kotlin.query.RealmResults+import io.realm.kotlin.types.RealmObject+import kotlin.time.Duration++/**+ * Automatically create an anonymous [Subscription] from a local query result in the background and+ * return the result of re-running the same query against the Realm file.+ *+ * This is a more streamlined alternative to doing something like this:+ *+ * ```+ * fun suspend getData(realm: Realm): RealmResults<Person> {+ *     val results = realm.query<Person>().find()+ *     realm.subscriptions.update { bgRealm ->+ *         add("myquery", results.query(""))+ *     }+ *     realm.subscriptions.waitForSynchronization()+ *     return realm.query<Person>().find()+ * }+ * ```+ *+ * It is possible to define whether or not to wait for the server to send all data before+ * running the local query. This is relevant as there might be delay from creating a subscription+ * to the data being available on the device due to either latency or because a large dataset needs+ * be downloaded.+ *+ * The default behaviour is that the first time `subscribe` is called, the query result will not+ * be returned until data has been downloaded from the server. On subsequent calls to `subscribe`+ * for the same query, the query will run immediately on the local database while any updates+ * are downloaded in the background.+ *+ * @param name name of the subscription. This can be used to identify it later in the [SubscriptionSet].+ * @param mode type of mode used to resolve the subscription. See [WaitForSync] for more details.
 * @param mode mode used to resolve the subscription. See [WaitForSync] for more details.
cmelchior

comment created time in an hour

Pull request review commentrealm/realm-kotlin

Improved subscribe API

+package io.realm.kotlin.test.mongodb.shared++import io.realm.kotlin.Realm+import io.realm.kotlin.entities.sync.flx.FlexChildObject+import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject+import io.realm.kotlin.entities.sync.flx.FlexParentObject+import io.realm.kotlin.ext.query+import io.realm.kotlin.internal.platform.runBlocking+import io.realm.kotlin.mongodb.ext.subscribe+import io.realm.kotlin.mongodb.subscriptions+import io.realm.kotlin.mongodb.sync.Subscription+import io.realm.kotlin.mongodb.sync.SubscriptionSetState+import io.realm.kotlin.mongodb.sync.SyncConfiguration+import io.realm.kotlin.mongodb.sync.WaitForSync+import io.realm.kotlin.mongodb.syncSession+import io.realm.kotlin.query.RealmResults+import io.realm.kotlin.test.mongodb.TEST_APP_FLEX+import io.realm.kotlin.test.mongodb.TestApp+import io.realm.kotlin.test.mongodb.createUserAndLogIn+import io.realm.kotlin.test.util.TestHelper+import io.realm.kotlin.test.util.use+import kotlinx.coroutines.TimeoutCancellationException+import kotlin.random.Random+import kotlin.test.AfterTest+import kotlin.test.BeforeTest+import kotlin.test.Test+import kotlin.test.assertEquals+import kotlin.test.assertFailsWith+import kotlin.test.assertNotEquals+import kotlin.test.assertNull+import kotlin.time.Duration.Companion.nanoseconds+import kotlin.time.Duration.Companion.seconds++/**+ * Class for testing the various extension methods we have for bridging the gap between Subscriptions+ * and RealmQuery/RealmResults.+ */+class SubscriptionExtensionsTests {++    private lateinit var app: TestApp+    private lateinit var realm: Realm++    @BeforeTest+    fun setup() {+        app = TestApp(appName = TEST_APP_FLEX)+        val (email, password) = TestHelper.randomEmail() to "password1234"+        val user = runBlocking {+            app.createUserAndLogIn(email, password)+        }+        val config = SyncConfiguration.Builder(+            user,+            schema = setOf(FlexParentObject::class, FlexChildObject::class, FlexEmbeddedObject::class)+        )+            .build()+        realm = Realm.open(config)+    }++    @AfterTest+    fun tearDown() {+        if (this::realm.isInitialized && !realm.isClosed()) {+            realm.close()+        }+        if (this::app.isInitialized) {+            app.close()+        }+    }++    @Test+    fun realmQuery_subscribe_anonymous() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        val results: RealmResults<FlexParentObject> = realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that subscribing twice to a query will result in the same subscription+    @Test+    fun realmQuery_subscribe_anonymousTwice() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that anonymous RealmQuery and RealmResults .subscribe calls result in the same sub.
    // Check that anonymous RealmQuery and RealmResults `subscribe` calls result in the same sub.
cmelchior

comment created time in an hour

Pull request review commentrealm/realm-kotlin

Improved subscribe API

+package io.realm.kotlin.test.mongodb.shared++import io.realm.kotlin.Realm+import io.realm.kotlin.entities.sync.flx.FlexChildObject+import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject+import io.realm.kotlin.entities.sync.flx.FlexParentObject+import io.realm.kotlin.ext.query+import io.realm.kotlin.internal.platform.runBlocking+import io.realm.kotlin.mongodb.ext.subscribe+import io.realm.kotlin.mongodb.subscriptions+import io.realm.kotlin.mongodb.sync.Subscription+import io.realm.kotlin.mongodb.sync.SubscriptionSetState+import io.realm.kotlin.mongodb.sync.SyncConfiguration+import io.realm.kotlin.mongodb.sync.WaitForSync+import io.realm.kotlin.mongodb.syncSession+import io.realm.kotlin.query.RealmResults+import io.realm.kotlin.test.mongodb.TEST_APP_FLEX+import io.realm.kotlin.test.mongodb.TestApp+import io.realm.kotlin.test.mongodb.createUserAndLogIn+import io.realm.kotlin.test.util.TestHelper+import io.realm.kotlin.test.util.use+import kotlinx.coroutines.TimeoutCancellationException+import kotlin.random.Random+import kotlin.test.AfterTest+import kotlin.test.BeforeTest+import kotlin.test.Test+import kotlin.test.assertEquals+import kotlin.test.assertFailsWith+import kotlin.test.assertNotEquals+import kotlin.test.assertNull+import kotlin.time.Duration.Companion.nanoseconds+import kotlin.time.Duration.Companion.seconds++/**+ * Class for testing the various extension methods we have for bridging the gap between Subscriptions+ * and RealmQuery/RealmResults.+ */+class SubscriptionExtensionsTests {++    private lateinit var app: TestApp+    private lateinit var realm: Realm++    @BeforeTest+    fun setup() {+        app = TestApp(appName = TEST_APP_FLEX)+        val (email, password) = TestHelper.randomEmail() to "password1234"+        val user = runBlocking {+            app.createUserAndLogIn(email, password)+        }+        val config = SyncConfiguration.Builder(+            user,+            schema = setOf(FlexParentObject::class, FlexChildObject::class, FlexEmbeddedObject::class)+        )+            .build()+        realm = Realm.open(config)+    }++    @AfterTest+    fun tearDown() {+        if (this::realm.isInitialized && !realm.isClosed()) {+            realm.close()+        }+        if (this::app.isInitialized) {+            app.close()+        }+    }++    @Test+    fun realmQuery_subscribe_anonymous() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        val results: RealmResults<FlexParentObject> = realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that subscribing twice to a query will result in the same subscription+    @Test+    fun realmQuery_subscribe_anonymousTwice() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that anonymous RealmQuery and RealmResults .subscribe calls result in the same sub.+    @Test+    fun anonymousSubscriptionsOverlap() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().find().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Verify that the realm query doesn't run against a frozen version previous to the Realm+    // being updated from `waitForSynchronization`.+    @Test+    fun realmQuery_subscribe_queryResultIsLatestVersion() = runBlocking {+        // Write data to a server Realm+        val section = Random.nextInt()+        val (email, password) = TestHelper.randomEmail() to "password1234"+        val user1 = app.createUserAndLogIn(email, password)+        val config = SyncConfiguration.Builder(+            user1,+            schema = setOf(FlexParentObject::class, FlexChildObject::class, FlexEmbeddedObject::class)+        ).initialSubscriptions { realm: Realm ->+            realm.query<FlexParentObject>("section = $0", section).subscribe()+        }.build()++        Realm.open(config).use { realmFromAnotherDevice ->+            realmFromAnotherDevice.writeBlocking {+                copyToRealm(FlexParentObject(section))+            }+            realmFromAnotherDevice.syncSession.uploadAllLocalChanges(30.seconds)+        }++        // Data still hasn't reached this device+        assertEquals(0, realm.query<FlexParentObject>().count().find())+        // Check that subscribing to a query, will run the query on the data downloaded from+        // the server and not just local data, due to WaitForSync.FIRST_TIME being the default.+        val result = realm.query<FlexParentObject>("section = $0", section).subscribe()+        assertEquals(1, result.size)+        assertEquals(section, result.first().section)+    }++    @Test+    fun realmQuery_subscribe_waitFirstTime() = runBlocking<Unit> {+        // Unnamed+        realm.query<FlexParentObject>().subscribe() // Default value is WaitForSync.FIRST_TIME+        var updatedSubs = realm.subscriptions+        assertEquals(1, updatedSubs.size)+        assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+        var sub: Subscription = updatedSubs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals("FlexParentObject", sub.objectType)++        // Named+        realm.query<FlexParentObject>().subscribe("my-name") // Default value is WaitForSync.FIRST_TIME+        updatedSubs = realm.subscriptions+        assertEquals(2, updatedSubs.size)+        assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+        sub = updatedSubs.last()+        assertEquals("my-name", sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals("FlexParentObject", sub.objectType)+    }++    @Test+    fun realmQuery_subscribe_waitNever() = runBlocking {+        // Un-named+        realm.query<FlexParentObject>().subscribe(mode = WaitForSync.NEVER)+        var updatedSubs = realm.subscriptions+        assertEquals(1, updatedSubs.size)+        // Updating the subscription will happen in the background, but+        // hopefully hasn't reached COMPLETE yet.+        assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)++        // Named+        realm.query<FlexParentObject>().subscribe(name = "my-name", mode = WaitForSync.NEVER)+        updatedSubs = realm.subscriptions+        assertEquals(2, updatedSubs.size)+        // Updating the subscription will happen in the background, but+        // hopefully hasn't reached COMPLETE yet.+        assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+    }++    @Test+    fun realmQuery_subscribe_waitAlways() = runBlocking {

Wouldn't it be safer to use the pattern from realmQuery_subscribe_queryResultIsLatestVersion to verify that you always get a result reflecting writes from another "device"?

cmelchior

comment created time in an hour

Pull request review commentrealm/realm-kotlin

Improved subscribe API

+@file:Suppress("invisible_reference", "invisible_member")+package io.realm.kotlin.mongodb.ext++import io.realm.kotlin.Realm+import io.realm.kotlin.internal.RealmImpl+import io.realm.kotlin.internal.getRealm+import io.realm.kotlin.mongodb.annotations.ExperimentalFlexibleSyncApi+import io.realm.kotlin.mongodb.internal.AppImpl+import io.realm.kotlin.mongodb.subscriptions+import io.realm.kotlin.mongodb.sync.Subscription+import io.realm.kotlin.mongodb.sync.SubscriptionSet+import io.realm.kotlin.mongodb.sync.SyncConfiguration+import io.realm.kotlin.mongodb.sync.WaitForSync+import io.realm.kotlin.mongodb.syncSession+import io.realm.kotlin.query.RealmQuery+import io.realm.kotlin.query.RealmResults+import io.realm.kotlin.types.RealmObject+import kotlinx.coroutines.CoroutineDispatcher+import kotlinx.coroutines.withContext+import kotlinx.coroutines.withTimeout+import kotlin.time.Duration++/**+ * Automatically create an anonymous [Subscription] from a query in the background and return the+ * result of running the same query against the local Realm file.+ *+ * This is a more streamlined alternative to doing something like this:+ *+ * ```+ * fun suspend getData(realm: Realm): RealmResults<Person> {+ *     realm.subscriptions.update { bgRealm ->+ *         add(bgRealm.query<Person>())+ *     }+ *     realm.subscriptions.waitForSynchronization()+ *     return realm.query<Person>().find()+ * }+ * ```+ *+ * It is possible to define whether or not to wait for the server to send all data before+ * running the local query. This is relevant as there might be delay from creating a subscription+ * to the data being available on the device due to either latency or because a large dataset needs+ * be downloaded.+ *+ * The default behaviour is that the first time `subscribe` is called, the query result will not+ * be returned until data has been downloaded from the server. On subsequent calls to `subscribe`+ * for the same query, the query will run immediately on the local database while any updates+ * are downloaded in the background.+ *+ * @param mode type of mode used to resolve the subscription. See [WaitForSync] for more details.+ * @param timeout How long to wait for the server to return the objects defined by the subscription.+ * This is only relevant for [WaitForSync.ALWAYS] and [WaitForSync.FIRST_TIME].+ * @return The result of running the query against the local Realm file. The results returned will+ * depend on which [mode] was used.+ * @throws kotlinx.coroutines.TimeoutCancellationException if the specified timeout was hit before+ * a query result could be returned.+ * @Throws IllegalStateException if this method is called on a Realm that isn't using Flexible Sync.+ */+@ExperimentalFlexibleSyncApi+public suspend fun <T : RealmObject> RealmQuery<T>.subscribe(+    mode: WaitForSync = WaitForSync.FIRST_TIME,+    timeout: Duration = Duration.INFINITE+): RealmResults<T> {+    return createSubscriptionFromQuery(this, null, false, mode, timeout)+}++/**+ * Automatically create a named [Subscription] from a query in the background and return the+ * result of running the same query against the local Realm file.+ *+ * This is a more streamlined alternative to doing something like this:+ *+ * ```+ * fun suspend getData(realm: Realm): RealmResults<Person> {+ *     realm.subscriptions.update { bgRealm ->+ *         add("myquery", bgRealm.query<Person>())+ *     }+ *     realm.subscriptions.waitForSynchronization()+ *     return realm.query<Person>().find()+ * }+ * ```+ *+ * It is possible to define whether or not to wait for the server to send all data before+ * running the local query. This is relevant as there might be delay from creating a subscription+ * to the data being available on the device due to either latency or because a large dataset needs+ * be downloaded.+ *+ * The default behaviour is that the first time `subscribe` is called, the query result will not+ * be returned until data has been downloaded from the server. On subsequent calls to `subscribe`+ * for the same query, the query will run immediately on the local database while any updates+ * are downloaded in the background.+ *+ * @param name name of the subscription. This can be used to identify it later in the [SubscriptionSet].+ * @param mode type of mode used to resolve the subscription. See [WaitForSync] for more details.+ * @param timeout How long to wait for the server to return the objects defined by the subscription.+ * This is only relevant for [WaitForSync.ALWAYS] and [WaitForSync.FIRST_TIME].+ * @return The result of running the query against the local Realm file. The results returned will+ * depend on which [mode] was used.+ * @throws kotlinx.coroutines.TimeoutCancellationException if the specified timeout was hit before+ * a query result could be returned.+ * @Throws IllegalStateException if this method is called on a Realm that isn't using Flexible Sync.+ */+@ExperimentalFlexibleSyncApi+public suspend fun <T : RealmObject> RealmQuery<T>.subscribe(+    name: String,+    updateExisting: Boolean = false,+    mode: WaitForSync = WaitForSync.FIRST_TIME,+    timeout: Duration = Duration.INFINITE+): RealmResults<T> {+    return createSubscriptionFromQuery(this, name, updateExisting, mode, timeout)+}++private suspend fun <T : RealmObject> createSubscriptionFromQuery(+    query: RealmQuery<T>,+    name: String?,+    updateExisting: Boolean = false,+    mode: WaitForSync,+    timeout: Duration+): RealmResults<T> {++    if (query !is io.realm.kotlin.internal.query.ObjectQuery<T>) {+        throw IllegalStateException("Only queries on objects are supported. This was: ${query::class}")+    }+    if (query.realmReference.owner !is RealmImpl) {+        throw IllegalStateException("Calling `subscribe()` inside a write transaction is not allowed.")+    }+    val realm: Realm = query.getRealm()+    val subscriptions = realm.subscriptions+    val appDispatcher: CoroutineDispatcher = ((realm.configuration as SyncConfiguration).user.app as AppImpl).appNetworkDispatcher.dispatcher

I guess we should like the above if called on a non-SyncConfiguration?

cmelchior

comment created time in 2 hours

Pull request review commentrealm/realm-kotlin

Improved subscribe API

+package io.realm.kotlin.test.mongodb.shared++import io.realm.kotlin.Realm+import io.realm.kotlin.entities.sync.flx.FlexChildObject+import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject+import io.realm.kotlin.entities.sync.flx.FlexParentObject+import io.realm.kotlin.ext.query+import io.realm.kotlin.internal.platform.runBlocking+import io.realm.kotlin.mongodb.ext.subscribe+import io.realm.kotlin.mongodb.subscriptions+import io.realm.kotlin.mongodb.sync.Subscription+import io.realm.kotlin.mongodb.sync.SubscriptionSetState+import io.realm.kotlin.mongodb.sync.SyncConfiguration+import io.realm.kotlin.mongodb.sync.WaitForSync+import io.realm.kotlin.mongodb.syncSession+import io.realm.kotlin.query.RealmResults+import io.realm.kotlin.test.mongodb.TEST_APP_FLEX+import io.realm.kotlin.test.mongodb.TestApp+import io.realm.kotlin.test.mongodb.createUserAndLogIn+import io.realm.kotlin.test.util.TestHelper+import io.realm.kotlin.test.util.use+import kotlinx.coroutines.TimeoutCancellationException+import kotlin.random.Random+import kotlin.test.AfterTest+import kotlin.test.BeforeTest+import kotlin.test.Test+import kotlin.test.assertEquals+import kotlin.test.assertFailsWith+import kotlin.test.assertNotEquals+import kotlin.test.assertNull+import kotlin.time.Duration.Companion.nanoseconds+import kotlin.time.Duration.Companion.seconds++/**+ * Class for testing the various extension methods we have for bridging the gap between Subscriptions+ * and RealmQuery/RealmResults.+ */+class SubscriptionExtensionsTests {++    private lateinit var app: TestApp+    private lateinit var realm: Realm++    @BeforeTest+    fun setup() {+        app = TestApp(appName = TEST_APP_FLEX)+        val (email, password) = TestHelper.randomEmail() to "password1234"+        val user = runBlocking {+            app.createUserAndLogIn(email, password)+        }+        val config = SyncConfiguration.Builder(+            user,+            schema = setOf(FlexParentObject::class, FlexChildObject::class, FlexEmbeddedObject::class)+        )+            .build()+        realm = Realm.open(config)+    }++    @AfterTest+    fun tearDown() {+        if (this::realm.isInitialized && !realm.isClosed()) {+            realm.close()+        }+        if (this::app.isInitialized) {+            app.close()+        }+    }++    @Test+    fun realmQuery_subscribe_anonymous() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        val results: RealmResults<FlexParentObject> = realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that subscribing twice to a query will result in the same subscription+    @Test+    fun realmQuery_subscribe_anonymousTwice() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that anonymous RealmQuery and RealmResults .subscribe calls result in the same sub.+    @Test+    fun anonymousSubscriptionsOverlap() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().find().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Verify that the realm query doesn't run against a frozen version previous to the Realm+    // being updated from `waitForSynchronization`.+    @Test+    fun realmQuery_subscribe_queryResultIsLatestVersion() = runBlocking {+        // Write data to a server Realm+        val section = Random.nextInt()+        val (email, password) = TestHelper.randomEmail() to "password1234"+        val user1 = app.createUserAndLogIn(email, password)+        val config = SyncConfiguration.Builder(+            user1,+            schema = setOf(FlexParentObject::class, FlexChildObject::class, FlexEmbeddedObject::class)+        ).initialSubscriptions { realm: Realm ->+            realm.query<FlexParentObject>("section = $0", section).subscribe()+        }.build()++        Realm.open(config).use { realmFromAnotherDevice ->+            realmFromAnotherDevice.writeBlocking {+                copyToRealm(FlexParentObject(section))+            }+            realmFromAnotherDevice.syncSession.uploadAllLocalChanges(30.seconds)+        }++        // Data still hasn't reached this device+        assertEquals(0, realm.query<FlexParentObject>().count().find())+        // Check that subscribing to a query, will run the query on the data downloaded from+        // the server and not just local data, due to WaitForSync.FIRST_TIME being the default.+        val result = realm.query<FlexParentObject>("section = $0", section).subscribe()+        assertEquals(1, result.size)+        assertEquals(section, result.first().section)+    }++    @Test+    fun realmQuery_subscribe_waitFirstTime() = runBlocking<Unit> {+        // Unnamed+        realm.query<FlexParentObject>().subscribe() // Default value is WaitForSync.FIRST_TIME+        var updatedSubs = realm.subscriptions+        assertEquals(1, updatedSubs.size)+        assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+        var sub: Subscription = updatedSubs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals("FlexParentObject", sub.objectType)++        // Named+        realm.query<FlexParentObject>().subscribe("my-name") // Default value is WaitForSync.FIRST_TIME+        updatedSubs = realm.subscriptions+        assertEquals(2, updatedSubs.size)+        assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+        sub = updatedSubs.last()+        assertEquals("my-name", sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals("FlexParentObject", sub.objectType)+    }++    @Test+    fun realmQuery_subscribe_waitNever() = runBlocking {+        // Un-named+        realm.query<FlexParentObject>().subscribe(mode = WaitForSync.NEVER)+        var updatedSubs = realm.subscriptions+        assertEquals(1, updatedSubs.size)+        // Updating the subscription will happen in the background, but+        // hopefully hasn't reached COMPLETE yet.+        assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)++        // Named+        realm.query<FlexParentObject>().subscribe(name = "my-name", mode = WaitForSync.NEVER)+        updatedSubs = realm.subscriptions+        assertEquals(2, updatedSubs.size)+        // Updating the subscription will happen in the background, but+        // hopefully hasn't reached COMPLETE yet.+        assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+    }++    @Test+    fun realmQuery_subscribe_waitAlways() = runBlocking {+        val sectionId = Random.nextInt()+        val results1 = realm.query<FlexParentObject>("section = $0", sectionId).subscribe() // Default value is WaitForSync.FIRST_TIME+        assertEquals(0, results1.size)+        uploadServerData(sectionId, 5)+        // Since the subscription is already present, we cannot control if the data is downloaded+        // before creating the next subscription. Instead we pause the syncSession and verify+        // that WaitForSync.ALWAYS timeout during network failures and resuming the session should+        // then work+        realm.syncSession.pause()+        assertFailsWith<TimeoutCancellationException> {+            realm.query<FlexParentObject>("section = $0", sectionId).subscribe(timeout = 3.seconds, mode = WaitForSync.ALWAYS)+        }+        realm.syncSession.resume()+        val results2 = realm.query<FlexParentObject>("section = $0", sectionId).subscribe(mode = WaitForSync.ALWAYS)+        assertEquals(5, results2.size)+    }++    @Test+    fun realmQuery_subscribe_timeOut_fails() = runBlocking<Unit> {+        assertFailsWith<TimeoutCancellationException> {+            realm.query<FlexParentObject>().subscribe(timeout = 1.nanoseconds)

Should we also test the named-case?

cmelchior

comment created time in an hour

Pull request review commentrealm/realm-kotlin

Improved subscribe API

+package io.realm.kotlin.test.mongodb.shared++import io.realm.kotlin.Realm+import io.realm.kotlin.entities.sync.flx.FlexChildObject+import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject+import io.realm.kotlin.entities.sync.flx.FlexParentObject+import io.realm.kotlin.ext.query+import io.realm.kotlin.internal.platform.runBlocking+import io.realm.kotlin.mongodb.ext.subscribe+import io.realm.kotlin.mongodb.subscriptions+import io.realm.kotlin.mongodb.sync.Subscription+import io.realm.kotlin.mongodb.sync.SubscriptionSetState+import io.realm.kotlin.mongodb.sync.SyncConfiguration+import io.realm.kotlin.mongodb.sync.WaitForSync+import io.realm.kotlin.mongodb.syncSession+import io.realm.kotlin.query.RealmResults+import io.realm.kotlin.test.mongodb.TEST_APP_FLEX+import io.realm.kotlin.test.mongodb.TestApp+import io.realm.kotlin.test.mongodb.createUserAndLogIn+import io.realm.kotlin.test.util.TestHelper+import io.realm.kotlin.test.util.use+import kotlinx.coroutines.TimeoutCancellationException+import kotlin.random.Random+import kotlin.test.AfterTest+import kotlin.test.BeforeTest+import kotlin.test.Test+import kotlin.test.assertEquals+import kotlin.test.assertFailsWith+import kotlin.test.assertNotEquals+import kotlin.test.assertNull+import kotlin.time.Duration.Companion.nanoseconds+import kotlin.time.Duration.Companion.seconds++/**+ * Class for testing the various extension methods we have for bridging the gap between Subscriptions+ * and RealmQuery/RealmResults.+ */+class SubscriptionExtensionsTests {++    private lateinit var app: TestApp+    private lateinit var realm: Realm++    @BeforeTest+    fun setup() {+        app = TestApp(appName = TEST_APP_FLEX)+        val (email, password) = TestHelper.randomEmail() to "password1234"+        val user = runBlocking {+            app.createUserAndLogIn(email, password)+        }+        val config = SyncConfiguration.Builder(+            user,+            schema = setOf(FlexParentObject::class, FlexChildObject::class, FlexEmbeddedObject::class)+        )+            .build()+        realm = Realm.open(config)+    }++    @AfterTest+    fun tearDown() {+        if (this::realm.isInitialized && !realm.isClosed()) {+            realm.close()+        }+        if (this::app.isInitialized) {+            app.close()+        }+    }++    @Test+    fun realmQuery_subscribe_anonymous() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        val results: RealmResults<FlexParentObject> = realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that subscribing twice to a query will result in the same subscription+    @Test+    fun realmQuery_subscribe_anonymousTwice() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that anonymous RealmQuery and RealmResults .subscribe calls result in the same sub.+    @Test+    fun anonymousSubscriptionsOverlap() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().find().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Verify that the realm query doesn't run against a frozen version previous to the Realm+    // being updated from `waitForSynchronization`.+    @Test+    fun realmQuery_subscribe_queryResultIsLatestVersion() = runBlocking {+        // Write data to a server Realm+        val section = Random.nextInt()+        val (email, password) = TestHelper.randomEmail() to "password1234"+        val user1 = app.createUserAndLogIn(email, password)+        val config = SyncConfiguration.Builder(+            user1,+            schema = setOf(FlexParentObject::class, FlexChildObject::class, FlexEmbeddedObject::class)+        ).initialSubscriptions { realm: Realm ->+            realm.query<FlexParentObject>("section = $0", section).subscribe()+        }.build()++        Realm.open(config).use { realmFromAnotherDevice ->+            realmFromAnotherDevice.writeBlocking {+                copyToRealm(FlexParentObject(section))+            }+            realmFromAnotherDevice.syncSession.uploadAllLocalChanges(30.seconds)+        }++        // Data still hasn't reached this device+        assertEquals(0, realm.query<FlexParentObject>().count().find())+        // Check that subscribing to a query, will run the query on the data downloaded from+        // the server and not just local data, due to WaitForSync.FIRST_TIME being the default.+        val result = realm.query<FlexParentObject>("section = $0", section).subscribe()+        assertEquals(1, result.size)+        assertEquals(section, result.first().section)+    }++    @Test+    fun realmQuery_subscribe_waitFirstTime() = runBlocking<Unit> {

There aren't really any logic testing that it is actually waitFirstTime'ing here are there 🤔 It looks like that it is the above test that actually tests that, at least data wise. Are we testing the effect on the subscription set? ... and shouldn't it then show that we are not COMPLETE after updating an existing subscription.

cmelchior

comment created time in an hour

Pull request review commentrealm/realm-kotlin

Improved subscribe API

+package io.realm.kotlin.test.mongodb.shared++import io.realm.kotlin.Realm+import io.realm.kotlin.entities.sync.flx.FlexChildObject+import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject+import io.realm.kotlin.entities.sync.flx.FlexParentObject+import io.realm.kotlin.ext.query+import io.realm.kotlin.internal.platform.runBlocking+import io.realm.kotlin.mongodb.ext.subscribe+import io.realm.kotlin.mongodb.subscriptions+import io.realm.kotlin.mongodb.sync.Subscription+import io.realm.kotlin.mongodb.sync.SubscriptionSetState+import io.realm.kotlin.mongodb.sync.SyncConfiguration+import io.realm.kotlin.mongodb.sync.WaitForSync+import io.realm.kotlin.mongodb.syncSession+import io.realm.kotlin.query.RealmResults+import io.realm.kotlin.test.mongodb.TEST_APP_FLEX+import io.realm.kotlin.test.mongodb.TestApp+import io.realm.kotlin.test.mongodb.createUserAndLogIn+import io.realm.kotlin.test.util.TestHelper+import io.realm.kotlin.test.util.use+import kotlinx.coroutines.TimeoutCancellationException+import kotlin.random.Random+import kotlin.test.AfterTest+import kotlin.test.BeforeTest+import kotlin.test.Test+import kotlin.test.assertEquals+import kotlin.test.assertFailsWith+import kotlin.test.assertNotEquals+import kotlin.test.assertNull+import kotlin.time.Duration.Companion.nanoseconds+import kotlin.time.Duration.Companion.seconds++/**+ * Class for testing the various extension methods we have for bridging the gap between Subscriptions+ * and RealmQuery/RealmResults.+ */+class SubscriptionExtensionsTests {++    private lateinit var app: TestApp+    private lateinit var realm: Realm++    @BeforeTest+    fun setup() {+        app = TestApp(appName = TEST_APP_FLEX)+        val (email, password) = TestHelper.randomEmail() to "password1234"+        val user = runBlocking {+            app.createUserAndLogIn(email, password)+        }+        val config = SyncConfiguration.Builder(+            user,+            schema = setOf(FlexParentObject::class, FlexChildObject::class, FlexEmbeddedObject::class)+        )+            .build()+        realm = Realm.open(config)+    }++    @AfterTest+    fun tearDown() {+        if (this::realm.isInitialized && !realm.isClosed()) {+            realm.close()+        }+        if (this::app.isInitialized) {+            app.close()+        }+    }++    @Test+    fun realmQuery_subscribe_anonymous() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        val results: RealmResults<FlexParentObject> = realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that subscribing twice to a query will result in the same subscription+    @Test+    fun realmQuery_subscribe_anonymousTwice() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Check that anonymous RealmQuery and RealmResults .subscribe calls result in the same sub.+    @Test+    fun anonymousSubscriptionsOverlap() = runBlocking {+        val subs = realm.subscriptions+        assertEquals(0, subs.size)+        realm.query<FlexParentObject>().subscribe()+        realm.query<FlexParentObject>().find().subscribe()+        assertEquals(SubscriptionSetState.COMPLETE, subs.state)+        assertEquals(1, subs.size)+        val sub: Subscription = subs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals(FlexParentObject::class.simpleName, sub.objectType)+    }++    // Verify that the realm query doesn't run against a frozen version previous to the Realm+    // being updated from `waitForSynchronization`.+    @Test+    fun realmQuery_subscribe_queryResultIsLatestVersion() = runBlocking {+        // Write data to a server Realm+        val section = Random.nextInt()+        val (email, password) = TestHelper.randomEmail() to "password1234"+        val user1 = app.createUserAndLogIn(email, password)+        val config = SyncConfiguration.Builder(+            user1,+            schema = setOf(FlexParentObject::class, FlexChildObject::class, FlexEmbeddedObject::class)+        ).initialSubscriptions { realm: Realm ->+            realm.query<FlexParentObject>("section = $0", section).subscribe()+        }.build()++        Realm.open(config).use { realmFromAnotherDevice ->+            realmFromAnotherDevice.writeBlocking {+                copyToRealm(FlexParentObject(section))+            }+            realmFromAnotherDevice.syncSession.uploadAllLocalChanges(30.seconds)+        }++        // Data still hasn't reached this device+        assertEquals(0, realm.query<FlexParentObject>().count().find())+        // Check that subscribing to a query, will run the query on the data downloaded from+        // the server and not just local data, due to WaitForSync.FIRST_TIME being the default.+        val result = realm.query<FlexParentObject>("section = $0", section).subscribe()+        assertEquals(1, result.size)+        assertEquals(section, result.first().section)+    }++    @Test+    fun realmQuery_subscribe_waitFirstTime() = runBlocking<Unit> {+        // Unnamed+        realm.query<FlexParentObject>().subscribe() // Default value is WaitForSync.FIRST_TIME+        var updatedSubs = realm.subscriptions+        assertEquals(1, updatedSubs.size)+        assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+        var sub: Subscription = updatedSubs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals("FlexParentObject", sub.objectType)++        // Named+        realm.query<FlexParentObject>().subscribe("my-name") // Default value is WaitForSync.FIRST_TIME+        updatedSubs = realm.subscriptions+        assertEquals(2, updatedSubs.size)+        assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+        sub = updatedSubs.last()+        assertEquals("my-name", sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals("FlexParentObject", sub.objectType)+    }++    @Test+    fun realmQuery_subscribe_waitNever() = runBlocking {+        // Un-named+        realm.query<FlexParentObject>().subscribe(mode = WaitForSync.NEVER)+        var updatedSubs = realm.subscriptions+        assertEquals(1, updatedSubs.size)+        // Updating the subscription will happen in the background, but+        // hopefully hasn't reached COMPLETE yet.+        assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)++        // Named+        realm.query<FlexParentObject>().subscribe(name = "my-name", mode = WaitForSync.NEVER)+        updatedSubs = realm.subscriptions+        assertEquals(2, updatedSubs.size)+        // Updating the subscription will happen in the background, but+        // hopefully hasn't reached COMPLETE yet.+        assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+    }++    @Test+    fun realmQuery_subscribe_waitAlways() = runBlocking {+        val sectionId = Random.nextInt()+        val results1 = realm.query<FlexParentObject>("section = $0", sectionId).subscribe() // Default value is WaitForSync.FIRST_TIME+        assertEquals(0, results1.size)+        uploadServerData(sectionId, 5)+        // Since the subscription is already present, we cannot control if the data is downloaded+        // before creating the next subscription. Instead we pause the syncSession and verify+        // that WaitForSync.ALWAYS timeout during network failures and resuming the session should+        // then work+        realm.syncSession.pause()+        assertFailsWith<TimeoutCancellationException> {+            realm.query<FlexParentObject>("section = $0", sectionId).subscribe(timeout = 3.seconds, mode = WaitForSync.ALWAYS)+        }+        realm.syncSession.resume()+        val results2 = realm.query<FlexParentObject>("section = $0", sectionId).subscribe(mode = WaitForSync.ALWAYS)+        assertEquals(5, results2.size)+    }++    @Test+    fun realmQuery_subscribe_timeOut_fails() = runBlocking<Unit> {+        assertFailsWith<TimeoutCancellationException> {+            realm.query<FlexParentObject>().subscribe(timeout = 1.nanoseconds)+        }+    }++    @Test+    fun realmQuery_subscribe_throwsInsideWrite() {+        realm.writeBlocking {+            // `subscribe()` being a suspend function make in hard to call+            // subscribe inside a write, but we should still detect it.+            runBlocking {+                assertFailsWith<IllegalStateException> {+                    query<FlexParentObject>().subscribe()+                }+                assertFailsWith<IllegalStateException> {+                    query<FlexParentObject>().subscribe(name = "my-name")+                }+            }+        }+    }++    @Test+    fun realmResults_subscribe_waitFirstTime() = runBlocking {+        // Unnamed+        realm.query<FlexParentObject>().find().subscribe() // Default value is WaitForSync.FIRST_TIME+        var updatedSubs = realm.subscriptions+        assertEquals(1, updatedSubs.size)+        assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+        var sub: Subscription = updatedSubs.first()+        assertNull(sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals("FlexParentObject", sub.objectType)++        // Named+        realm.query<FlexParentObject>().find().subscribe("my-name") // Default value is WaitForSync.FIRST_TIME+        updatedSubs = realm.subscriptions+        assertEquals(2, updatedSubs.size)+        assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+        sub = updatedSubs.last()+        assertEquals("my-name", sub.name)+        assertEquals("TRUEPREDICATE ", sub.queryDescription)+        assertEquals("FlexParentObject", sub.objectType)+    }++    @Test+    fun realmResults_subscribe_waitOnNever() = runBlocking {+        // Un-named+        realm.query<FlexParentObject>().find().subscribe(mode = WaitForSync.NEVER)+        var updatedSubs = realm.subscriptions+        assertEquals(1, updatedSubs.size)+        // Updating the subscription will happen in the background, but+        // hopefully hasn't reached COMPLETE yet.+        assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)++        // Named+        realm.query<FlexParentObject>().find().subscribe(name = "my-name", mode = WaitForSync.NEVER)+        updatedSubs = realm.subscriptions+        assertEquals(2, updatedSubs.size)+        // Updating the subscription will happen in the background, but+        // hopefully hasn't reached COMPLETE yet.+        assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state)+    }++    @Test+    fun realmResults_subscribe_waitAlways() = runBlocking {+        val sectionId = Random.nextInt()+        val results1 = realm.query<FlexParentObject>("section = $0", sectionId).find().subscribe() // Default value is WaitForSync.FIRST_TIME+        assertEquals(0, results1.size)+        uploadServerData(sectionId, 5)+        // Since the subscription is already present, we cannot control if the data is downloaded+        // before creating the next subscription. Instead we pause the syncSession and verify+        // that WaitForSync.ALWAYS timeout during network failures and resuming the session should+        // then work+        realm.syncSession.pause()+        assertFailsWith<TimeoutCancellationException> {+            realm.query<FlexParentObject>("section = $0", sectionId).find().subscribe(timeout = 3.seconds, mode = WaitForSync.ALWAYS)+        }+        realm.syncSession.resume()+        val results2 = realm.query<FlexParentObject>("section = $0", sectionId).find().subscribe(mode = WaitForSync.ALWAYS)+        assertEquals(5, results2.size)+    }++    @Test+    fun realmResults_subscribe_subquery() = runBlocking {+        val topQueryResult: RealmResults<FlexParentObject> = realm.query<FlexParentObject>("section = 42").find()+        val subQueryResult: RealmResults<FlexParentObject> = topQueryResult.query("name == $0", "Jane").find()+        subQueryResult.subscribe()+        val subs = realm.subscriptions+        assertEquals(1, subs.size)+        assertEquals("section == 42 and name == \"Jane\" ", subs.first().queryDescription)+        subQueryResult.subscribe("my-name")+        assertEquals(2, subs.size)+        val lastSub = subs.last()+        assertEquals("my-name", lastSub.name)+        assertEquals("section == 42 and name == \"Jane\" ", lastSub.queryDescription)+    }++    @Test+    fun realmResults_subscribe_timeOut_fails() = runBlocking<Unit> {+        assertFailsWith<TimeoutCancellationException> {+            realm.query<FlexParentObject>().find().subscribe(timeout = 1.nanoseconds)

Again, any reason for not also testing the named variant?

cmelchior

comment created time in an hour

Pull request review commentrealm/realm-kotlin

Improved subscribe API

+@file:Suppress("invisible_reference", "invisible_member")+package io.realm.kotlin.mongodb.ext++import io.realm.kotlin.mongodb.annotations.ExperimentalFlexibleSyncApi+import io.realm.kotlin.mongodb.sync.Subscription+import io.realm.kotlin.mongodb.sync.SubscriptionSet+import io.realm.kotlin.mongodb.sync.WaitForSync+import io.realm.kotlin.query.RealmQuery+import io.realm.kotlin.query.RealmResults+import io.realm.kotlin.types.RealmObject+import kotlin.time.Duration++/**+ * Automatically create an anonymous [Subscription] from a local query result in the background and+ * return the result of re-running the same query against the Realm file.+ *+ * This is a more streamlined alternative to doing something like this:+ *+ * ```+ * fun suspend getData(realm: Realm): RealmResults<Person> {+ *     val results = realm.query<Person>().find()+ *     realm.subscriptions.update { bgRealm ->+ *         add("myquery", results.query(""))+ *     }+ *     realm.subscriptions.waitForSynchronization()+ *     return realm.query<Person>().find()+ * }+ * ```+ *+ * It is possible to define whether or not to wait for the server to send all data before+ * running the local query. This is relevant as there might be delay from creating a subscription+ * to the data being available on the device due to either latency or because a large dataset needs+ * be downloaded.+ *+ * The default behaviour is that the first time `subscribe` is called, the query result will not+ * be returned until data has been downloaded from the server. On subsequent calls to `subscribe`+ * for the same query, the query will run immediately on the local database while any updates+ * are downloaded in the background.+ *+ * @param name name of the subscription. This can be used to identify it later in the [SubscriptionSet].+ * @param mode type of mode used to resolve the subscription. See [WaitForSync] for more details.+ * @param timeout How long to wait for the server to return the objects defined by the subscription.+ * This is only relevant for [WaitForSync.ALWAYS] and [WaitForSync.FIRST_TIME].+ * @return The result of running the query against the local Realm file. The results returned will+ * depend on which [mode] was used.+ * @throws kotlinx.coroutines.TimeoutCancellationException if the specified timeout was hit before+ * a query result could be returned.+ * @Throws IllegalStateException if this method is called on a Realm that isn't using Flexible Sync.+ */@ExperimentalFlexibleSyncApi+public suspend fun <T : RealmObject> RealmResults<T>.subscribe(+    mode: WaitForSync = WaitForSync.FIRST_TIME,+    timeout: Duration = Duration.INFINITE+): RealmResults<T> {+    val query: RealmQuery<T> = this.query("")+    return query.subscribe(mode, timeout)+}++/**+ * Automatically create a named [Subscription] from a local query result in the background and+ * return the result of re-running the same query against the Realm file.+ *+ * This is a more streamlined alternative to doing something like this:+ *+ * ```+ * fun suspend getData(realm: Realm): RealmResults<Person> {+ *     val results = realm.query<Person>().find()+ *     realm.subscriptions.update { bgRealm ->+ *         add("myquery", results.query(""))+ *     }+ *     realm.subscriptions.waitForSynchronization()+ *     return realm.query<Person>().find()+ * }+ * ```+ *+ * It is possible to define whether or not to wait for the server to send all data before+ * running the local query. This is relevant as there might be delay from creating a subscription+ * to the data being available on the device due to either latency or because a large dataset needs+ * be downloaded.+ *+ * The default behaviour is that the first time `subscribe` is called, the query result will not+ * be returned until data has been downloaded from the server. On subsequent calls to `subscribe`+ * for the same query, the query will run immediately on the local database while any updates+ * are downloaded in the background.+ *+ * @param name name of the subscription. This can be used to identify it later in the [SubscriptionSet].+ * @param mode type of mode used to resolve the subscription. See [WaitForSync] for more details.
 * @param mode mode used to resolve the subscription. See [WaitForSync] for more details.
cmelchior

comment created time in an hour

PullRequestReviewEvent
more