From e499184ebdb5a432337c69c95c7fca154dc6fba9 Mon Sep 17 00:00:00 2001 From: cbullinger Date: Fri, 20 Oct 2023 18:21:01 -0400 Subject: [PATCH] Initial draft --- .../mongodb/realm/realmkmmapp/SchemaSync.kt | 107 ++- .../realm/realmkmmapp/SchemaSyncTest.kt | 251 ++++++ ...ync.snippet.sync-define-embedded-object.kt | 20 + ...nippet.sync-define-inverse-relationship.kt | 15 + ...maSync.snippet.sync-define-realm-object.kt | 14 + ...nippet.sync-define-to-many-relationship.kt | 14 + ...snippet.sync-define-to-one-relationship.kt | 14 + .../schemas/define-realm-object-model.txt | 13 + .../schemas/model-data-device-sync.txt | 766 ++++++++++++++++++ source/sdk/kotlin/sync/add-sync-to-app.txt | 16 +- 10 files changed, 1225 insertions(+), 5 deletions(-) create mode 100644 examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/SchemaSyncTest.kt create mode 100644 source/examples/generated/kotlin/SchemaSync.snippet.sync-define-embedded-object.kt create mode 100644 source/examples/generated/kotlin/SchemaSync.snippet.sync-define-inverse-relationship.kt create mode 100644 source/examples/generated/kotlin/SchemaSync.snippet.sync-define-realm-object.kt create mode 100644 source/examples/generated/kotlin/SchemaSync.snippet.sync-define-to-many-relationship.kt create mode 100644 source/examples/generated/kotlin/SchemaSync.snippet.sync-define-to-one-relationship.kt create mode 100644 source/sdk/kotlin/realm-database/schemas/model-data-device-sync.txt diff --git a/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/SchemaSync.kt b/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/SchemaSync.kt index 14b7b78203a..e0c4b8b4715 100644 --- a/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/SchemaSync.kt +++ b/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/SchemaSync.kt @@ -1,6 +1,9 @@ package com.mongodb.realm.realmkmmapp +import io.realm.kotlin.ext.backlinks import io.realm.kotlin.ext.realmListOf +import io.realm.kotlin.query.RealmResults +import io.realm.kotlin.types.EmbeddedRealmObject import io.realm.kotlin.types.RealmInstant import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmObject @@ -10,6 +13,10 @@ import org.mongodb.kbson.ObjectId // :replace-start: { // "terms": { // "ExampleSyncObject_": "", +// "ExampleSyncRelationship_": "", +// "_1": "", +// "_2": "", +// "_3": "", // "SyncTask": "Task" // } // } @@ -44,10 +51,106 @@ class Team : RealmObject { } // :snippet-end: +/* +****** Used on Model Data with Device Sync page ****** +** Tested on SchemaSyncTest.kt ** +*/ + +// :snippet-start: sync-define-realm-object +// Maps to `Frog` collection +class ExampleSyncObject_Frog : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + var age: Int? = null +} + +// Maps to `Pond` collection +class ExampleSyncObject_Pond : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" +} +// :snippet-end: + +// :snippet-start: sync-define-to-one-relationship +class ExampleSyncRelationship_Pond : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" +} + +class ExampleSyncRelationship_Frog : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + var age: Int? = null + // To-one relationship (MUST be optional) + var favoritePond: ExampleSyncRelationship_Pond? = null +} +// :snippet-end: + +// :snippet-start: sync-define-to-many-relationship +class ExampleSyncRelationship_Pond_1 : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" +} + +class ExampleSyncRelationship_Frog_1 : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + var age: Int? = null + // To-many relationship (can have many ponds) + var favoritePonds: RealmList = realmListOf() +} +// :snippet-end: + +// :snippet-start: sync-define-inverse-relationship +class ExampleSyncRelationship_Pond_2 : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + // Backlink to the `Frog` that has this `Pond` as its favorite + val frog: RealmResults by backlinks(ExampleSyncRelationship_Frog_2::favoritePonds) +} +class ExampleSyncRelationship_Frog_2 : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + var age: Int? = null + // To-many relationship (can have many ponds) + var favoritePonds: RealmList = realmListOf() +} +// :snippet-end: + +// :snippet-start: sync-define-embedded-object +class ExampleSyncRelationship_Frog_3 : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + var age: Int? = null + // Embed a single object (MUST be optional) + var favoritePond: ExampleSyncRelationship_EmbeddedPond? = null +} + +class ExampleSyncRelationship_Forest : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + // Embed multiple objects (can have many ponds) + var forestPonds: RealmList = realmListOf() +} + +class ExampleSyncRelationship_EmbeddedPond : EmbeddedRealmObject { + var name: String? = null +} +// :snippet-end: /* -** Used on Add Sync to App page ** - */ +****** Used on Add Sync to App page ****** +*/ // :snippet-start: sync-to-do-model class ExampleSyncObject_List : RealmObject { diff --git a/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/SchemaSyncTest.kt b/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/SchemaSyncTest.kt new file mode 100644 index 00000000000..b2b5877fe65 --- /dev/null +++ b/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/SchemaSyncTest.kt @@ -0,0 +1,251 @@ +package com.mongodb.realm.realmkmmapp + +import io.realm.kotlin.Realm +import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmListOf +import io.realm.kotlin.mongodb.App +import io.realm.kotlin.mongodb.Credentials +import io.realm.kotlin.mongodb.sync.SyncConfiguration +import io.realm.kotlin.mongodb.syncSession +import kotlinx.coroutines.runBlocking +import org.mongodb.kbson.ObjectId +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.time.Duration.Companion.seconds + +class SchemaSyncTest : RealmTest() { + /* + ** Object types defined in SchemaSync.kt ** + */ + @Test + fun syncRealmObjectTest() { + val credentials = Credentials.anonymous(reuseExisting = false) + runBlocking { + val app = App.create(FLEXIBLE_APP_ID) + val user = app.login(credentials) + val flexSyncConfig = SyncConfiguration.Builder(user, setOf(ExampleSyncObject_Frog::class, ExampleSyncObject_Pond::class)) + .initialSubscriptions { realm -> + add(realm.query(), "all-frogs") + add(realm.query(), "all-ponds") + } + .build() + val syncRealm = Realm.open(flexSyncConfig) + Log.v("Successfully opened realm: ${syncRealm.configuration}") + + val frogId = ObjectId() + val pondId = ObjectId() + syncRealm.write { + val existingFrogs = query().find() + delete(existingFrogs) + val existingPonds = query().find() + delete(existingPonds) + copyToRealm(ExampleSyncObject_Frog().apply { + _id = frogId + name = "Kermit" + age = 42 + }) + copyToRealm(ExampleSyncObject_Pond().apply { + _id = pondId + name = "Frog Pond" + + }) + } + syncRealm.syncSession.uploadAllLocalChanges(30.seconds) + syncRealm.write { + val frog = query("_id == $0", frogId).find().first() + val pond = query("_id == $0", pondId).find().first() + delete(frog) + delete(pond) + } + user.delete() + syncRealm.close() + } + } + + @Test + fun syncToOneRelationshipTest() { + val credentials = Credentials.anonymous(reuseExisting = false) + runBlocking { + val app = App.create(FLEXIBLE_APP_ID) + val user = app.login(credentials) + val flexSyncConfig = SyncConfiguration.Builder(user, setOf(ExampleSyncRelationship_Frog::class, ExampleSyncRelationship_Pond::class)) + .initialSubscriptions { realm -> + add(realm.query()) + add(realm.query()) + } + .build() + val syncRealm = Realm.open(flexSyncConfig) + Log.v("Successfully opened realm: ${syncRealm.configuration}") + + val frogId = ObjectId() + val pondId = ObjectId() + syncRealm.write { + val existingFrogs = query().find() + delete(existingFrogs) + val existingPonds = query().find() + delete(existingPonds) + copyToRealm(ExampleSyncRelationship_Frog().apply { + _id = frogId + name = "Kermit" + age = 42 + favoritePond = copyToRealm(ExampleSyncRelationship_Pond().apply { + _id = pondId + name = "Frog Pond" + }) + }) + } + syncRealm.syncSession.uploadAllLocalChanges(30.seconds) + syncRealm.write { + val frog = query("_id == $0", frogId).find().first() + val pond = query("_id == $0", pondId).find().first() + delete(frog) + delete(pond) + } + user.delete() + syncRealm.close() + } + } + + @Test + fun syncToManyRelationshipTest() { + val credentials = Credentials.anonymous(reuseExisting = false) + runBlocking { + val app = App.create(FLEXIBLE_APP_ID) + val user = app.login(credentials) + val flexSyncConfig = SyncConfiguration.Builder(user, setOf(ExampleSyncRelationship_Frog_1::class, ExampleSyncRelationship_Pond_1::class)) + .initialSubscriptions { realm -> + add(realm.query()) + add(realm.query()) + } + .build() + val syncRealm = Realm.open(flexSyncConfig) + Log.v("Successfully opened realm: ${syncRealm.configuration}") + + val frogId = ObjectId() + val pondId = ObjectId() + syncRealm.write { + val existingFrogs = query().find() + delete(existingFrogs) + val existingPonds = query().find() + delete(existingPonds) + copyToRealm(ExampleSyncRelationship_Frog_1().apply { + _id = frogId + name = "Kermit" + age = 42 + favoritePonds = realmListOf(copyToRealm(ExampleSyncRelationship_Pond_1().apply { + _id = pondId + name = "Frog Pond" + })) + }) + } + syncRealm.syncSession.uploadAllLocalChanges(30.seconds) + syncRealm.write { + val frog = query("_id == $0", frogId).find().first() + val pond = query("_id == $0", pondId).find().first() + delete(frog) + delete(pond) + } + user.delete() + syncRealm.close() + } + } + + @Test + fun syncInverseRelationshipTest() { + val credentials = Credentials.anonymous(reuseExisting = false) + runBlocking { + val app = App.create(FLEXIBLE_APP_ID) + val user = app.login(credentials) + val flexSyncConfig = SyncConfiguration.Builder(user, setOf(ExampleSyncRelationship_Frog_2::class, ExampleSyncRelationship_Pond_2::class)) + .initialSubscriptions { realm -> + add(realm.query()) + add(realm.query()) + } + .build() + val syncRealm = Realm.open(flexSyncConfig) + Log.v("Successfully opened realm: ${syncRealm.configuration}") + + val frogId = ObjectId() + val pondId = ObjectId() + syncRealm.write { + val existingFrogs = query().find() + delete(existingFrogs) + val existingPonds = query().find() + delete(existingPonds) + copyToRealm(ExampleSyncRelationship_Frog_2().apply { + _id = frogId + name = "Kermit" + age = 42 + favoritePonds = realmListOf(copyToRealm(ExampleSyncRelationship_Pond_2().apply { + _id = pondId + name = "Frog Pond" + })) + }) + } + syncRealm.syncSession.uploadAllLocalChanges(30.seconds) + syncRealm.write { + val frog = query("_id == $0", frogId).find().first() + val pond = query("_id == $0", pondId).find().first() + assertEquals(pond.frog.first(), frog) + delete(frog) + delete(pond) + } + user.delete() + syncRealm.close() + } + } + + @Test + fun syncEmbeddedRelationshipTest() { + val credentials = Credentials.anonymous(reuseExisting = false) + runBlocking { + val app = App.create(FLEXIBLE_APP_ID) + val user = app.login(credentials) + val flexSyncConfig = SyncConfiguration.Builder(user, setOf(ExampleSyncRelationship_Frog_3::class, ExampleSyncRelationship_Forest::class, ExampleSyncRelationship_EmbeddedPond::class)) + .initialSubscriptions { realm -> + add(realm.query()) + add(realm.query()) + } + .build() + val syncRealm = Realm.open(flexSyncConfig) + Log.v("Successfully opened realm: ${syncRealm.configuration}") + + val frogId = ObjectId() + val forestId = ObjectId() + syncRealm.write { + val existingFrogs = query().find() + delete(existingFrogs) + val existingForests = query().find() + delete(existingForests) + val existingPonds = query().find() + delete(existingPonds) + copyToRealm(ExampleSyncRelationship_Frog_3().apply { + _id = frogId + name = "Kermit" + age = 42 + favoritePond = ExampleSyncRelationship_EmbeddedPond().apply { + name = "Frog Pond" + } + }) + copyToRealm(ExampleSyncRelationship_Forest().apply { + _id = forestId + name = "Frog Forest" + forestPonds = realmListOf(ExampleSyncRelationship_EmbeddedPond().apply { + name = "Frog Pond" + }) + }) + } + syncRealm.syncSession.uploadAllLocalChanges(30.seconds) + syncRealm.write { + val frog = query("_id == $0", frogId).find().first() + val forest = query("_id == $0", forestId).find().first() + assertEquals(frog.favoritePond?.name, "Frog Pond") + assertEquals(forest.forestPonds.first().name, "Frog Pond") + delete(frog) + delete(forest) + } + user.delete() + syncRealm.close() + } + } +} \ No newline at end of file diff --git a/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-embedded-object.kt b/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-embedded-object.kt new file mode 100644 index 00000000000..18750328ee7 --- /dev/null +++ b/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-embedded-object.kt @@ -0,0 +1,20 @@ +class Frog : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + var age: Int? = null + // Embed a single object (MUST be optional) + var favoritePond: EmbeddedPond? = null +} + +class Forest : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + // Embed multiple objects (can have many ponds) + var forestPonds: RealmList = realmListOf() +} + +class EmbeddedPond : EmbeddedRealmObject { + var name: String? = null +} diff --git a/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-inverse-relationship.kt b/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-inverse-relationship.kt new file mode 100644 index 00000000000..7efaf4d609f --- /dev/null +++ b/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-inverse-relationship.kt @@ -0,0 +1,15 @@ +class Pond : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + // Backlink to the `Frog` that has this `Pond` as its favorite + val frog: RealmResults by backlinks(Frog::favoritePonds) +} +class Frog : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + var age: Int? = null + // To-many relationship (can have many ponds) + var favoritePonds: RealmList = realmListOf() +} diff --git a/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-realm-object.kt b/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-realm-object.kt new file mode 100644 index 00000000000..7b7cd9ae728 --- /dev/null +++ b/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-realm-object.kt @@ -0,0 +1,14 @@ +// Maps to `Frog` collection +class Frog : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + var age: Int? = null +} + +// Maps to `Pond` collection +class Pond : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" +} diff --git a/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-to-many-relationship.kt b/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-to-many-relationship.kt new file mode 100644 index 00000000000..c6adc3f582d --- /dev/null +++ b/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-to-many-relationship.kt @@ -0,0 +1,14 @@ +class Pond : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" +} + +class Frog : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + var age: Int? = null + // To-many relationship (can have many ponds) + var favoritePonds: RealmList = realmListOf() +} diff --git a/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-to-one-relationship.kt b/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-to-one-relationship.kt new file mode 100644 index 00000000000..9818d000e49 --- /dev/null +++ b/source/examples/generated/kotlin/SchemaSync.snippet.sync-define-to-one-relationship.kt @@ -0,0 +1,14 @@ +class Pond : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" +} + +class Frog : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "" + var age: Int? = null + // To-one relationship (MUST be optional) + var favoritePond: Pond? = null +} diff --git a/source/sdk/kotlin/realm-database/schemas/define-realm-object-model.txt b/source/sdk/kotlin/realm-database/schemas/define-realm-object-model.txt index ec21a64855d..6c2d08ad1b2 100644 --- a/source/sdk/kotlin/realm-database/schemas/define-realm-object-model.txt +++ b/source/sdk/kotlin/realm-database/schemas/define-realm-object-model.txt @@ -5,6 +5,13 @@ Define a Realm Object Model - Kotlin SDK ======================================== +.. meta:: + :keywords: code example, android, kmm, kmp, ios, data model + +.. facet:: + :name: genre + :values: tutorial + .. contents:: On this page :local: :backlinks: none @@ -25,6 +32,12 @@ To learn about how to make changes to your Realm objects after defining your Realm object model, refer to :ref:`Change an Object Model `. +.. note:: Define Data Model with Device Sync + + If your app uses Atlas Device Sync, there are additional considerations + when defining your data model. For more information, refer to + :ref:`kotlin-model-data-device-sync`. + .. _kotlin-object-type: .. _kotlin-object-schema: diff --git a/source/sdk/kotlin/realm-database/schemas/model-data-device-sync.txt b/source/sdk/kotlin/realm-database/schemas/model-data-device-sync.txt new file mode 100644 index 00000000000..141379b841c --- /dev/null +++ b/source/sdk/kotlin/realm-database/schemas/model-data-device-sync.txt @@ -0,0 +1,766 @@ +.. _kotlin-model-data-device-sync: + +======================================== +Model Data with Device Sync - Kotlin SDK +======================================== + +.. meta:: + :keywords: code example, coroutine, android, kmm, kmp, ios + +.. facet:: + :name: genre + :values: tutorial + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +This page describes the Device Sync data model and how it's used to map data from +the App Services schema used by Device Sync to the Realm schema used +by the Kotlin SDK in the client. + +This page *does not*: + +- Explain how to define a Realm object model in the client app. To learn how, refer to :ref:``. +- Explain how to set up Device Sync on a client app. To learn how, refer to :ref:``. + + For a detailed explanation of Device Sync, refer to :ref:`realm-sync-get-started` in the Atlas App Services documentation. + +.. tip:: + + The table in the :ref:`` + section on this page provides a list of supported data types and how they map between Realm and App Services. For a more comprehensive resource, refer to :ref:`` in the App Services documentation. + +.. _kotlin-define-device-sync-data-model: + +Define a Device Sync Data Model +------------------------------- + +The Atlas Device Sync data model contains a matching set +of schemas that define your data in two formats: + +- **Realm schema**: the client-side object model in your app that + defines your data as Kotlin classes using the Kotlin SDK. +- **App Services schema**: the server-side schema in Atlas App Services that + defines your data in BSON. For more information, refer to :ref:`` in + the App Services documentation. + +Device Sync uses these schemas to validate and map objects in +Kotlin and BSON format when syncing data: + +- When you sync data from the client, Device Sync automatically + converts the Realm Kotlin data types to BSON. +- Then, when the client device syncs data from Atlas via Device + Sync, the SDK converts the BSON data back to Kotlin objects. + +To use Device Sync, you must define both a Realm schema and +an App Services schema. Both schemas must be consistent with each other to sync data. + +You can define your schema in the client app +or in Atlas first, depending on your preference and use case. +You can then generate a corresponding schema with matching +object models. + +Generate a Schema with Development Mode +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are developing a new client application, you likely want to +iterate on the data model in the client app. After you +:ref:`define an object model ` +directly in your client app code, you can enable Development Mode in App +Services to generate a matching App Services schema automatically. + +**Development Mode** is a configuration setting that allows Device Sync +to infer and update schemas based on client-side data models when you +sync data from the client. For more information, refer to +:ref:`` in the App Services documentation. + +.. important:: Breaking Schema Changes + + Development Mode does not work with breaking schema changes, so you + must remove the existing schema from the server when you make breaking + to the SDK data model. For more information, refer to + :ref:`` in the App Services + documentation for more information. + +Generate a Schema with Existing Atlas Data +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are developing a client application that works with data that already +exists in Atlas, you can generate a schema from that data, and then generate +SDK object models to use in your Kotlin client app. To learn more, refer to +:ref:`` in the App Services documentation. + +Map Realm Objects +----------------- + +Realm objects are the uniquely named instances of Kotlin classes defined in your Realm schema that determine the properties and relationships for +objects of that type. + +.. note:: Primary Key _id Required for Synced Objects + + To work with Atlas Device Sync, your Realm objects must have a :ref:`primary key ` field called ``_id``. It can be of type ``String``, ``Int``, or ``ObjectId``. Device Sync uses this to + identify objects in the Atlas collections. + +Realm object map to App Services schemas in the following ways: + +- Realm object names map to collections in your linked Device Sync data source. +- The Realm object schema maps to an App Services schema within that collection. + +Note that embedded objects are *not* stored in their own collection in Atlas. This is because they cannot exist outside of their parent object type. Refer to the :ref:`` section on this page for more information. + +.. tip:: Map with Development Mode + + When Development Mode is enabled, App Services automatically + creates a collection and schema for each new Realm object + type that you sync. + +In the following example, we have ``Frog`` and ``Pond`` +objects with basic properties defined in our Realm schema: + +.. literalinclude:: /examples/generated/kotlin/SchemaSync.snippet.sync-define-realm-object.kt + :language: kotlin + :caption: Frog and Pond Realm Objects + +We can then see these object types mapped to corresponding +App Services schemas in separate respective Frog and Pond collections, as well as sample +frog and pond objects that conform to the data model: + +.. tabs:: + + .. tab:: App Services Schema + :tabid: app-services-schema + + .. code-block:: json + :caption: Frog collection in App Services + + { + "title": "Frog", + "type": "object", + "required": [ + "_id", + "name" + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "age": { + "bsonType": "long" + }, + "name": { + "bsonType": "string" + } + } + } + + .. code-block:: json + :caption: Pond collection in App Services + + { + "title": "Pond", + "type": "object", + "required": [ + "_id", + "name" + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "name": { + "bsonType": "string" + } + } + } + + .. tab:: Atlas Objects + :tabid: app-services-objects + + .. code-block:: json + :caption: Frog and Pond objects in Atlas + + // Example Frog object + { + "_id": ObjectId("5af712eff26b29dc5c51c60f"), + "name": "Kermit", + "age": 42 + } + + // Example Pond object + { + "_id": ObjectId("5af714eff24b294c5251cf04"), + "name": "Kermit's Pond" + } + +Map Realm Relationships +----------------------- + +Your Realm object model might include relationships between +objects. There are two primary types of relationships: + +- To-one relationship: an object is related in a specific way to no more than one other Realm object. +- To-many relationship: an object is related in a specific way to multiple Realm objects. + +Relationships are mapped by properties that reference the +other Realm object's primary key. + +For more information on modeling relationships in an +App Services schema, refer to :ref:`` +in the App Services documentation. +.. _kotlin-device-sync-to-one-relationship: + +Map To-One Relationships +~~~~~~~~~~~~~~~~~~~~~~~~ + +A to-one relationship relationship maps one property to a single +instance of another Realm object. To-one relationships *must* +be optional. For more information on how +to-one relationships are defined in Kotlin SDK, refer to +:ref:``. + +Using the objects in the example above, consider a case where a +``Frog`` can have one favorite pond. We can add a +``favoritePond`` property to our ``Frog`` model +that is an optional link to a ``Pond`` object. + +.. literalinclude:: /examples/generated/kotlin/SchemaSync.snippet.sync-define-to-one-relationship.kt + :language: kotlin + :caption: Frog with To-One Relationship to Pond + :emphasize-lines: 12-13 + +In the App Services schema, we see the new property translates to a +field ``favoritePond``: + +- The field is not in the ``required`` array because it is an optional property. +- Its type is an ``objectId`` that links to a specific ``Pond`` object in the separate ``Pond`` collection. This is because we defined the primary key on our ``Pond`` model as an ``objectId``. + +.. code-block:: json + :caption: App Services Schema + :emphasize-lines: 15-17 + + { + "title": "Frog", + "type": "object", + "required": [ + "_id", + "name" + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "age": { + "bsonType": "long" + }, + "favoritePond": { + "bsonType": "objectId" + }, + "name": { + "bsonType": "string" + } + } + } + +The ``Pond`` schema doesn't change. Because this is a to-one +relationship, it's a one-way relationship; the ``Pond`` has no +relationship back to ``Frog``. + +.. _kotlin-device-sync-to-many-relationship: + +To-Many Relationship +~~~~~~~~~~~~~~~~~~~~ + +A to-many relationship relationship maps one property to zero or +more instances of another Realm object. For more information on how +to-many relationships are defined in Kotlin SDK, refer to +:ref:``. + +Consider another case where a +``Frog`` can have many favorite ponds instead of only one. We add a ``favoritePonds`` property +to our ``Frog`` model that is a list of ``Pond`` objects. If the +frog has no favorite ponds, this is an empty list. As the frog gets favorite ponds, +we can create new ``Pond`` objects and append them to the frog's +``favoritePonds`` list. + +.. literalinclude:: /examples/generated/kotlin/SchemaSync.snippet.sync-define-to-many-relationship.kt + :language: kotlin + :caption: Frog with To-Many Relationship to Pond + :emphasize-lines: 12-13 + +In the App Services schema, we see the new property translates to a +``favoritePonds`` field that contains all of the ``Pond`` objects related to the ``Frog`` object: + +- The field is not in the ``required`` array because it is an optional property. +- The type of this field is an array of type ``objectId``. This is because we defined the primary key on our ``Pond`` model as an ``objectId``. + +.. code-block:: json + :caption: App Services Schema + :emphasize-lines: 15-20 + + { + "title": "Frog", + "type": "object", + "required": [ + "_id", + "name" + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "age": { + "bsonType": "long" + }, + "favoritePonds": { + "bsonType": "array", + "items": { + "bsonType": "objectId" + } + }, + "name": { + "bsonType": "string" + } + } + } + +Again, the ``Pond`` schema doesn't change because the ``Pond`` has +no relationship back to ``Frog``. + +.. _kotlin-device-sync-inverse-relationship: + +Inverse Relationship +~~~~~~~~~~~~~~~~~~~~ + +An inverse relationship links an object back to any other objects +that refer to it in a defined to-one or to-many relationship called +a backlink. +For more information on how inverse relationships are defined in +Kotlin SDK, refer to :ref:``. + +App Services schemas *do not* support inverse relationships. +This is because inverse relationship represent an implicit +relationship in Realm that is automatically updated when the +backlink is modified. +This means that you cannot directly set the value of an +inverse relationship, and the relationship does not exist in Atlas. +Instead, Realm derives and updates those relationships for you +in the client application based on your Realm object model. + +Consider a case where +the ``Pond`` object has an inverse relationship to the ``Frog`` +object. + +.. literalinclude:: /examples/generated/kotlin/SchemaSync.snippet.sync-define-inverse-relationship.kt + :language: kotlin + :caption: Pond with Inverse Relationship to Frog + :emphasize-lines: 5-6 + +In the App Services schema, we see the ``Frog`` has the to-many +relationship to the ``Pond`` through the ``favoritePonds`` property. However, the ``frog`` property that +represents the inverse relationship to a ``Frog`` from our ``Pond`` +model is *not* present. This is because the inverse relationship cannot be +explicitly defined in Atlas. However, Realm will update it automatically whenever you add or remove an object from +the relationship. + +.. code-block:: json + :caption: App Services Schema + + // Pond object DOES NOT contain the inverse relationship + { + "title": "Pond", + "type": "object", + "required": [ + "_id", + "name" + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "name": { + "bsonType": "string" + } + } + } + + { + "title": "Frog", + "type": "object", + "required": [ + "_id", + "name" + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "age": { + "bsonType": "long" + }, + "favoritePonds": { + "bsonType": "array", + "items": { + "bsonType": "objectId" + } + }, + "name": { + "bsonType": "string" + } + } + } + +.. _kotlin-device-sync-embedded-object: + +Map Embedded Objects +-------------------- + +Embedded objects represent nested data inside of a single, +specific parent object. You can reference an embedded object type +from parent object types in the same way as you would define a +relationship. For more information on how embedded objects are +defined in Kotlin SDK, refer to +:ref:``. + +However, unlike regular Realm objects, embedded objects are *not* stored in their +own collection in Atlas. Instead, they are stored as part of the +parent object's document and are not accessible outside of the parent object. + +In the following example, we have a ``Frog`` object with a ``favoritePond`` +property that references a single embedded ``Pond`` object and a ``Forest`` +object with ``forestPonds`` relationship property that references a list of +many embedded ``Pond`` objects: + +.. literalinclude:: /examples/generated/kotlin/SchemaSync.snippet.sync-define-embedded-object.kt + :language: kotlin + :caption: Frog and Forest with Embedded Pond Relationships + :emphasize-lines: 6-7, 14-15 + +In the App Services schema, we see the embedded objects map to +documents in the parent type's schema + +.. code-block:: json + :caption: App Services Schema + :emphasize-lines: 15-24, 42-54 + + { + "title": "Frog", + "type": "object", + "required": [ + "_id", + "name" + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "age": { + "bsonType": "long" + }, + "favoritePond": { + "title": "EmbeddedPond", + "type": "object", + "required": [], + "properties": { + "name": { + "bsonType": "string" + } + } + }, + "name": { + "bsonType": "string" + } + } + } + + { + "title": "Forest", + "type": "object", + "required": [ + "_id", + "name" + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "forestPonds": { + "bsonType": "array", + "items": { + "title": "EmbeddedPond", + "type": "object", + "required": [], + "properties": { + "name": { + "bsonType": "string" + } + } + } + }, + "name": { + "bsonType": "string" + } + } + } + +.. _kotlin-device-sync-schema-types-table: + +Schema Types Mapping List +------------------------- + +The following tables demonstrate how Realm object types map to a +corresponding App Services schema BSON type. For a complete list of supported +App Services schema types and their mappings and available properties, refer +to :ref:`` in the App Services documentation. + +Kotlin Types +~~~~~~~~~~~~ + +The following table lists the supported Kotlin data types and +examples of how the declared properties map between a Realm schema and an App +Services schema. + +For more information on the Kotlin data types supported by the Kotlin SDK and +how to define them in your data model, refer to :ref:`Kotlin Data +Types `. + +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :widths: 20 40 40 + + * - Kotlin Data Type + - Realm Object + - App Services Schema + + * - ``String`` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.string-required.kt + :language: kotlin + - .. code-block:: json + + "stringReq": { + "bsonType": "string" + } + + * - ``Byte`` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.byte-required.kt + :language: kotlin + - .. code-block:: json + + "byteReq": { + "bsonType": "long" + } + + * - ``Short`` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.short-required.kt + :language: kotlin + - .. code-block:: json + + "shortReq": { + "bsonType": "long" + } + + * - ``Int`` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.int-required.kt + :language: kotlin + - .. code-block:: json + + "intReq": { + "bsonType": "long" + } + + * - ``Long`` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.long-required.kt + :language: kotlin + - .. code-block:: json + + "longReq": { + "bsonType": "long" + } + + * - ``Float`` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.float-required.kt + :language: kotlin + - .. code-block:: json + + "floatReq": { + "bsonType": "float" + } + + * - ``Double`` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.double-required.kt + :language: kotlin + - .. code-block:: json + + "doubleReq": { + "bsonType": "double" + } + + * - ``Boolean`` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.bool-required.kt + :language: kotlin + - .. code-block:: json + + "boolReq": { + "bsonType": "bool" + } + + * - ``Char`` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.char-required.kt + :language: kotlin + - .. code-block:: json + + "charReq": { + "bsonType": "long" + } + +BSON Types +~~~~~~~~~~ + +The following table lists the supported MongoDB BSON data types and +examples of how the declared properties map between a Realm schema and an App +Services schema. + +For more information on the MongoDB BSON data types supported by the Kotlin +SDK and how to define them in your data model, refer to :ref:`Kotlin Data +Types `. + +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :widths: 20 40 40 + + * - MongoDB BSON Type + - Realm Object + - App Services Schema + + * - :ref:`ObjectId ` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.objectId-required.kt + :language: kotlin + - .. code-block:: json + + "objectIdReq": { + "bsonType": "objectId" + } + + * - ``Decimal128`` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.decimal128-required.kt + :language: kotlin + - .. code-block:: json + + "decimal128Req": { + "bsonType": "decimal" + } + +Realm Types +~~~~~~~~~~~ + +The following table lists the supported Realm-specific data types and +examples of how the declared properties map between a Realm schema and an App +Services schema. + +For more information on the Realm-specific data types supported by the Kotlin +SDK and how to define them in your data model, refer to :ref:`Kotlin Data +Types `. + +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :widths: 20 40 40 + + * - Realm-Specific Type + - Realm Object + - App Services Schema + + * - :ref:`RealmUUID ` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.uuid-required.kt + :language: kotlin + - .. code-block:: json + + "uuidReq": { + "bsonType": "uuid" + } + + * - :ref:`RealmInstant ` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.realmInstant-required.kt + :language: kotlin + - .. code-block:: json + + "realmInstantReq": { + "bsonType": "date" + } + + * - :ref:`RealmAny ` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.realmAny-optional.kt + :language: kotlin + - .. code-block:: json + + "realmAnyOpt": { + "bsonType": "mixed" + } + + * - :ref:`MutableRealmInt ` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.mutableRealmInt-required.kt + :language: kotlin + - .. code-block:: json + + "mutableRealmIntReq": { + "bsonType": "long" + } + + * - :ref:`RealmList ` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.list-required.kt + :language: kotlin + - .. code-block:: json + + "listReq": { + "bsonType": "array", + "items": { + "bsonType": "uuid" + } + } + + * - :ref:`RealmSet ` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.set-required.kt + :language: kotlin + - .. code-block:: json + + "setReq": { + "bsonType": "array", + "uniqueItems": true, + "items": { + "bsonType": "string" + } + } + + * - :ref:`RealmDictionary ` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.dictionary-required.kt + :language: kotlin + - .. code-block:: json + + "dictionaryReq": { + "bsonType": "object", + "additionalProperties": { + "bsonType": "string" + } + } + + * - :ref:`RealmObject ` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.realm-object-optional.kt + :language: kotlin + - .. code-block:: json + + "realmInstantReq": { + "bsonType": "date" + } + + * - :ref:`EmbeddedRealmObject ` + - .. literalinclude:: /examples/generated/kotlin/DataTypes.snippet.embedded-object-optional.kt + :language: kotlin + - .. code-block:: json + + "realmInstantReq": { + "bsonType": "date" + } diff --git a/source/sdk/kotlin/sync/add-sync-to-app.txt b/source/sdk/kotlin/sync/add-sync-to-app.txt index 010ed3f2945..76da93f8fd4 100644 --- a/source/sdk/kotlin/sync/add-sync-to-app.txt +++ b/source/sdk/kotlin/sync/add-sync-to-app.txt @@ -6,6 +6,13 @@ Add Device Sync to an App - Kotlin SDK ============================================= +.. meta:: + :keywords: code example, android, kmm, kmp, ios, atlas device sync, mongodb atlas + +.. facet:: + :name: genre + :values: tutorial + .. contents:: On this page :local: :backlinks: none @@ -21,7 +28,6 @@ Already familiar with Device Sync? Skip ahead to the :ref:`` section to get started. - Device Sync ----------- @@ -73,6 +79,10 @@ in Atlas first: prefer to define your data model through Atlas first, refer to :ref:`` in the App Services documentation. +.. note:: Data Model Mapping in App Services + + To learn more about how data maps between the client and Atlas, refer to :ref:``. + Subscriptions ~~~~~~~~~~~~~ @@ -171,8 +181,8 @@ documentation. App Services documentation. For our example app, we enable Device Sync with Development Mode, and -then add the default "User can read and write all data -default" role. This means that, for an authorized user with a network +then add the default "User can read and write all data" +default role. This means that, for an authorized user with a network connection, Device Sync downloads eligible data to the client *and* Atlas permits writes to the client and then syncs them the backend. To learn more about what happens when an authorized user does not have