Docs Menu
Docs Home
/ /
Atlas Device SDKs
/ / /

Model Data with Device Sync - Kotlin SDK

On this page

  • Define a Device Sync Data Model
  • Generate a Schema from the Client App
  • Generate a Schema with Existing Atlas Data
  • Device Sync Requirements
  • Realm Object Mapping
  • Realm Relationship Mapping
  • Map To-One Relationships
  • To-Many Relationship
  • Inverse Relationship
  • Embedded Objects
  • Schema Types Mapping List
  • Kotlin Types
  • BSON Types
  • Realm Types

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 Define a Realm Object Model - Kotlin SDK.

  • Explain how to set up Device Sync on a client app. To learn how, refer to Add Device Sync to an App - Kotlin SDK.

For a detailed explanation of Device Sync, refer to Get Started with Atlas Device Sync in the Atlas App Services documentation.

Tip

The table in the Schema Types Mapping List section on this page provides a quick reference for supported data types and how they map between Realm and App Services. For a more comprehensive resource, refer to Data Model Mapping in the App Services documentation.

The Atlas Device Sync data model is defined by the following schemas, which allows Device Sync to map your data between the client and Atlas:

  • 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 Schemas in the App Services documentation.

Device Sync uses these schemas to validate and convert objects between 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 a Realm schema and an App Services schema, and both schemas must be consistent with each other.

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.

If you are developing a new client application, you likely want to iterate on the data model in the client app. After you 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 Development Mode in the App Services documentation.

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 Sync Data in Atlas with a Client Application in the App Services documentation.

There are a few requirements to successfully sync objects. As outlined earlier, you must have matching Realm and App Services schemas that contain the objects that you want to sync.

Additionally, Device Sync requires that:

  • Your object models must have a 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.

    If an object does not have an _id field defined, Realm throws the the following schema validation error: There must be a primary key property named '_id' on a synchronized Realm.

  • Each class must have at least one queryable field.

    When Development Mode is enabled, fields that you include in client subscription queries are automatically added as queryable fields in Atlas. For more information on configuring subscriptions with the Kotlin SDK, refer to Subscriptions Overview.

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.

App Services maps Realm objects to Atlas in the following ways:

  • Realm object names map to Atlas collections in your linked Device Sync data source. Note the following:

    • When Development Mode is enabled, App Services automatically creates a collection and schema for each new Realm object type that you sync.

    • 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 Embedded Objects section on this page for more information.

  • The Realm object schema maps to an App Services schema within its appropriate collection.

In the following example, we have Frog and Pond objects with basic properties defined in our Realm schema:

Frog and Pond Realm Objects
// 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 = ""
}

In Atlas, we can see how these object types map to corresponding App Services schemas -- each object maps to its respective collection. We also see example frog and pond objects that conform to this data model:

Frog collection in App Services
{
"title": "Frog",
"type": "object",
"required": [
"_id",
"name"
],
"properties": {
"_id": {
"bsonType": "objectId"
},
"age": {
"bsonType": "long"
},
"name": {
"bsonType": "string"
}
}
}
Pond collection in App Services
{
"title": "Pond",
"type": "object",
"required": [
"_id",
"name"
],
"properties": {
"_id": {
"bsonType": "objectId"
},
"name": {
"bsonType": "string"
}
}
}
Example Frog object in Atlas
{
"_id": ObjectId("5af712eff26b29dc5c51c60f"),
"name": "Kermit",
"age": 42
}
Example Pond object in Atlas
{
"_id": ObjectId("5af714eff24b294c5251cf04"),
"name": "Kermit's Pond"
}

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 Relationships in the App Services documentation.

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 Define a To-One Relationship Property.

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.

Frog with To-One Relationship to Pond
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
}

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.

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.

App Services Schema
{
"title": "Frog",
"type": "object",
"required": [
"_id",
"name"
],
"properties": {
"_id": {
"bsonType": "objectId"
},
"age": {
"bsonType": "long"
},
"favoritePond": {
"bsonType": "objectId"
},
"name": {
"bsonType": "string"
}
}
}

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 Define a To-Many Relationship Property.

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.

Frog with To-Many Relationship to Pond
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<Pond> = realmListOf()
}

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.

Again, that the Pond schema doesn't change because the Pond has no relationship back to Frog.

App Services Schema
{
"title": "Frog",
"type": "object",
"required": [
"_id",
"name"
],
"properties": {
"_id": {
"bsonType": "objectId"
},
"age": {
"bsonType": "long"
},
"favoritePonds": {
"bsonType": "array",
"items": {
"bsonType": "objectId"
}
},
"name": {
"bsonType": "string"
}
}
}

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 Define an Inverse Relationship.

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.

Pond with Inverse Relationship to Frog
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<Frog> 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<Pond> = realmListOf()
}

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.

App Services Schema
// `Pond` schema in App Services DOES NOT contain the
// `frog` inverse relationship property
{
"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"
}
}
}

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 Define an Embedded Object.

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:

Frog and Forest with Embedded Pond Relationships
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<EmbeddedPond> = realmListOf()
}
class EmbeddedPond : EmbeddedRealmObject {
var name: String? = null
}

In the App Services schema, we see the embedded objects map to documents in each parent type:

App Services Schema
{
"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"
}
}
}

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 Data Model Mapping in the App Services documentation.

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 Kotlin Data Types.

Kotlin Data Type
Realm Object
App Services Schema
String
var stringReq: String = ""
"stringReq": {
"bsonType": "string"
}
Byte
var byteReq: Byte = 0
"byteReq": {
"bsonType": "long"
}
Short
var shortReq: Short = 0
"shortReq": {
"bsonType": "long"
}
Int
var intReq: Int = 0
"intReq": {
"bsonType": "long"
}
Long
var longReq: Long = 0L
"longReq": {
"bsonType": "long"
}
Float
var floatReq: Float = 0.0f
"floatReq": {
"bsonType": "float"
}
Double
var doubleReq: Double = 0.0
"doubleReq": {
"bsonType": "double"
}
Boolean
var boolReq: Boolean = false
"boolReq": {
"bsonType": "bool"
}
Char
var charReq: Char = 'a'
"charReq": {
"bsonType": "long"
}

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 Kotlin Data Types.

MongoDB BSON Type
Realm Object
App Services Schema
var objectIdReq: ObjectId = ObjectId()
"objectIdReq": {
"bsonType": "objectId"
}
Decimal128
var decimal128Req: Decimal128 = Decimal128("123.456")
"decimal128Req": {
"bsonType": "decimal"
}

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 Kotlin Data Types.

Realm-Specific Type
Realm Object
App Services Schema
var uuidReq: RealmUUID = RealmUUID.random()
"uuidReq": {
"bsonType": "uuid"
}
var realmInstantReq: RealmInstant = RealmInstant.now()
"realmInstantReq": {
"bsonType": "date"
}
var realmAnyOpt: RealmAny? = RealmAny.create("foo")
"realmAnyOpt": {
"bsonType": "mixed"
}
var mutableRealmIntReq: MutableRealmInt = MutableRealmInt.create(0)
"mutableRealmIntReq": {
"bsonType": "long"
}
var listReq: RealmList<CustomObjectType> = realmListOf()
"listReq": {
"bsonType": "array",
"items": {
"bsonType": "uuid"
}
}
var setReq: RealmSet<String> = realmSetOf()
"setReq": {
"bsonType": "array",
"uniqueItems": true,
"items": {
"bsonType": "string"
}
}
var dictionaryReq: RealmDictionary<String> = realmDictionaryOf()
"dictionaryReq": {
"bsonType": "object",
"additionalProperties": {
"bsonType": "string"
}
}
var realmObjectPropertyOpt: CustomObjectType? = null
"realmObjectPropertyOpt": {
"bsonType": "<PRIMARY_KEY_TYPE>"
}
var embeddedProperty: EmbeddedObjectType? = null
"embeddedProperty": {
"title": "EmbeddedObjectType",
"type": "object",
"required": [],
"properties": {
"name": {
"bsonType": "string"
}
}
}

Back

Change an Object Model

Next

Configure & Open a Realm