Interested in speaking at MongoDB World 2022? Click here to become a speaker.
HomeLearnArticleNew Realm Data Types: Dictionaries/Maps, Sets, Mixed, and UUIDs

New Realm Data Types: Dictionaries/Maps, Sets, Mixed, and UUIDs

Published: Jun 15, 2021

  • Atlas
  • Mobile
  • Realm
  • ...

By Ian Ward

Rate this article

#TL;DR

Starting with Realm Javascript 10.5, Realm Cocoa 10.8, Realm .NET 10.2, and Realm Java 10.6, developers will be able persist and query new language specific data types in the Realm Database. These include Dictionaries/Maps, Sets, a Mixed type, and UUIDs.

#Introduction

We're excited to announce that the Realm SDK team has shipped four new data types for the Realm Mobile Database. This work – prioritized in response to community requests – continues to make using the Realm SDKs an intuitive, idiomatic experience for developers. It eliminates even more boilerplate code from your codebase, and brings the data layer closer to your app's source code.

These new types make it simple to model flexible data in Realm, and easier to work across Realm and MongoDB Atlas. Mobile developers who are building with Realm and MongoDB Realm Sync can leverage the flexibility of MongoDB's data structure in their offline-first mobile applications.

Read on to learn more about each of the four new data types we've released, and see examples of when and how to use them in your data modeling:

#Dictionaries/Maps

Dictionaries/maps allow developers to store data in arbitrary key-value pairs. They're used when a developer wants to add flexibility to data models that may evolve over time, or handle unstructured data from a remote endpoint. They also enable a mobile developer to store unique key-value pairs without needing to check the data for uniqueness before insertion.

Both Dictionaries and Maps can be useful when working with REST APIs, where extra data may be returned that's not defined in a mobile app's schema. Mobile developers who need to future-proof their schema against rapidly changing feature requirements and future product iterations will also find it useful to work with these new data types in Realm.

Consider a gaming app that has multiple games within it, and a single Player class. The developer building the app knows that future releases will need to enable new functionality, like a view of player statistics and a leaderboard. But the Player can serve in different roles for each available game. This makes defining a strict structure for player statistics difficult.

With Dictionary/Map data types, the developer can place a gameplayStats field on the Player class as a dictionary. Using this dictionary, it's simple to display a screen that shows the player's most common roles, the games they've competed in, and any relevant statistics that the developer wants to include on the leaderboard. After the leaderboard has been released and iterated on, the developer can look to migrate their Dictionary to a more formally structured class as part of a formal feature.

1import android.util.Log
2import io.realm.Realm
3import io.realm.RealmDictionary
4import io.realm.RealmObject
5import io.realm.kotlin.where
6import kotlinx.coroutines.flow.Flow
7import kotlinx.coroutines.flow.flow
8import java.util.AbstractMap
9
10open class Player : RealmObject() {
11 var name: String? = null
12 var email: String? = null
13 var playerHandle: String? = null
14 var gameplayStats: RealmDictionary<String> = RealmDictionary()
15 var competitionStats: RealmDictionary<String> = RealmDictionary()
16}
17
18realm.executeTransactionAsync { r: Realm ->
19 val player = Player()
20 player.playerHandle = "iDubs"
21 // get the RealmDictionary field from the object we just created and add stats
22 player.gameplayStats = RealmDictionary(mapOf<String, String>())
23 .apply {
24 "mostCommonRole" to "Medic"
25 "clan" to "Realmers"
26 "favoriteMap" to "Scorpian Bay"
27 "tagLine" to "Always be Healin"
28 "nemesisHandle" to "snakeCase4Life"
29 }
30 player.competitionStats = RealmDictionary(mapOf<String, String>()).apply {
31 "EastCoastInvitational" to "2nd Place"
32 "TransAtlanticOpen" to "4th Place"
33 }
34 r.insert(player)
35}
36
37// Developer implements a Competitions View -
38// emit all entries in the dictionary for view by the user
39val player = realm.where<Player>().equalTo("name", "iDubs").findFirst()
40player?.let {
41 player.competitionStats.addChangeListener { map, changes ->
42 val insertions = changes.insertions
43 for (insertion in insertions) {
44 Log.v("EXAMPLE", "Player placed at a new competition $insertion")
45 }
46 }
47}
48
49fun competitionFlow(): Flow<String> = flow {
50 for ((competition, place) in player!!.competitionStats) {
51 emit("$competition - $place")
52 }
53}
54
55// Build a RealmQuery that searches the Dictionary type
56val query = realm.where<Player>().equalTo("name", "iDubs")
57val entry = AbstractMap.SimpleEntry("nemesisHandle", "snakeCase4Life")
58val playerQuery = query.containsEntry("gameplayStats", entry).findFirst()
59
60// remove player nemesis - they are friends now!
61realm.executeTransaction { r: Realm ->
62 playerQuery?.gameplayStats?.remove("nemesisHandle")
63}

#Mixed

Realm's Mixed type allows any Realm primitive type to be stored in the database, helping developers when strict type-safety isn't appropriate. Developers may find this useful when dealing with data they don't have total control over – like receiving data and values from a third-party API. Mixed data types are also useful when dealing with legacy states that were stored with the incorrect types. Converting the type could break other APIs and create considerable work. With Mixed types, developers can avoid this difficulty and save hours of time.

We believe Mixed data types will be especially valuable for users who want to sync data between Realm and MongoDB Atlas. MongoDB's document-based data model allows a single field to support many types across documents. For users syncing data between Realm and Atlas, the new Mixed type allows developers to persist data of any valid Realm primitive type, or any Realm Object class reference. Developers don't risk crashing their app because a field value violated type-safety rules in Realm.

1import android.util.Log
2import io.realm.*
3import io.realm.kotlin.where
4
5open class Distributor : RealmObject() {
6 var name: String = ""
7 var transitPolicy: String = ""
8}
9
10open class Business : RealmObject() {
11 var name: String = ""
12 var deliveryMethod: String = ""
13}
14
15open class Individual : RealmObject() {
16 var name: String = ""
17 var salesTerritory: String = ""
18}
19
20open class Palette(var owner: RealmAny = RealmAny.nullValue()) : RealmObject() {
21 var scanId: String? = null
22 open fun ownerToString(): String {
23 return when (owner.type) {
24 RealmAny.Type.NULL -> {
25 "no owner"
26 }
27 RealmAny.Type.STRING -> {
28 owner.asString()
29 }
30 RealmAny.Type.OBJECT -> {
31 when (owner.valueClass) {
32 is Business -> {
33 val business = owner.asRealmModel(Business::class.java)
34 business.name
35 }
36 is Distributor -> {
37 val distributor = owner.asRealmModel(Distributor::class.java)
38 distributor.name
39 }
40 is Individual -> {
41 val individual = owner.asRealmModel(Individual::class.java)
42 individual.name
43 }
44 else -> "unknown type"
45 }
46 }
47 else -> {
48 "unknown type"
49 }
50 }
51 }
52}
53
54realm.executeTransaction { r: Realm ->
55 val newDistributor = r.copyToRealm(Distributor().apply {
56 name = "Warehouse R US"
57 transitPolicy = "Onsite Truck Pickup"
58 })
59 val paletteOne = r.copyToRealm(Palette().apply {
60 scanId = "A1"
61 })
62 // Add the owner of the palette as an object reference to another Realm class
63 paletteOne.owner = RealmAny.valueOf(newDistributor)
64 val newBusiness = r.copyToRealm(Business().apply {
65 name = "Mom and Pop"
66 deliveryMethod = "Cheapest Private Courier"
67 })
68 val paletteTwo = r.copyToRealm(Palette().apply {
69 scanId = "B2"
70 owner = RealmAny.valueOf(newBusiness)
71 })
72 val newIndividual = r.copyToRealm(Individual().apply {
73 name = "Traveling Salesperson"
74 salesTerritory = "DC Corridor"
75 })
76 val paletteThree = r.copyToRealm(Palette().apply {
77 scanId = "C3"
78 owner = RealmAny.valueOf(newIndividual)
79 })
80}
81
82// Get a reference to palette one
83val paletteOne = realm.where<Palette>()
84 .equalTo("scanId", "A1")
85 .findFirst()!!
86
87// Extract underlying Realm Object from RealmAny by casting it RealmAny.Type.OBJECT
88val ownerPaletteOne: Palette = paletteOne.owner.asRealmModel(Palette::class.java)
89Log.v("EXAMPLE", "Owner of Palette One: " + ownerPaletteOne.ownerToString())
90
91// Get a reference to the palette owned by Traveling Salesperson
92// so that you can remove ownership - they're broke!
93val salespersonPalette = realm.where<Palette>()
94 .equalTo("owner.name", "Traveling Salesperson")
95 .findFirst()!!
96
97val salesperson = realm.where<Individual>()
98 .equalTo("name", "Traveling Salesperson")
99 .findFirst()
100
101realm.executeTransaction { r: Realm ->
102 salespersonPalette.owner = RealmAny.nullValue()
103}
104
105val paletteTwo = realm.where<Palette>()
106 .equalTo("scanId", "B2")
107 .findFirst()!!
108
109// Set up a listener to see when Ownership changes for relabeling of palettes
110val listener = RealmObjectChangeListener { changedPalette: Palette, changeSet: ObjectChangeSet? ->
111 if (changeSet != null && changeSet.changedFields.contains("owner")) {
112 Log.i("EXAMPLE",
113 "Palette $'paletteTwo.scanId' has changed ownership.")
114 }
115}
116
117// Observe object notifications.
118paletteTwo.addChangeListener(listener)

#Sets

Sets allow developers to store an unordered array of unique values. This new data type in Realm opens up powerful querying and mutation capabilities with only a few lines of code.

With sets, you can compare data and quickly find matches. Sets in Realm have built-in methods for filtering and writing to a set that are unique to the type. Unique methods on the Set type include, isSubset(), contains(), intersects(), formIntersection, and formUnion(). Aggregation functions like min(), max(), avg(), and sum() can be used to find averages, sums, and similar.

Sets in Realm have the potential to eliminate hundreds of lines of gluecode. Consider an app that suggests expert speakers from different areas of study, who can address a variety of specific topics. The developer creates two classes for this use case: Expert and Topic. Each of these classes has a Set field of strings which defines the disciplines the user is an expert in, and the fields that the topic covers.

Sets will make the predicted queries easy for the developer to implement. An app user who is planning a Speaker Panel could see all experts who have knowledge of both "Autonomous Vehicles" and "City Planning." The application could also run a query that looks for experts in one or more of these disciples by using the built-in intersect method, and the user can use results to assemble a speaker panel.

Developers who are using MongoDB Realm Sync to keep data up-to-date between Realm and MongoDB Atlas are able to keep the semantics of a Set in place even when synchronizing data.

You can depend on the enforced uniqueness among the values of a Set. There's no need to check the array for a value match before performing an insertion, which is a common implementation pattern that any user of SQLite will be familiar with. The operations performed on Realm Set data types will be synced and translated to documents using the $addToSet group of operations on MongoDB, preserving uniqueness in arrays.

1import android.util.Log
2import io.realm.*
3import io.realm.kotlin.where
4
5open class Expert : RealmObject() {
6 var name: String = ""
7 var email: String = ""
8 var disciplines: RealmSet<String> = RealmSet<String>()
9}
10
11open class Topic : RealmObject() {
12 var name: String = ""
13 var location: String = ""
14 var discussionThemes: RealmSet<String> = RealmSet<String>()
15 var panelists: RealmList<Expert> = RealmList()
16}
17
18realm.executeTransaction { r: Realm ->
19 val newExpert = r.copyToRealm(Expert())
20 newExpert.name = "Techno King"
21 // get the RealmSet field from the object we just created
22 val disciplineSet = newExpert.disciplines
23 // add value to the RealmSet
24 disciplineSet.add("Trance")
25 disciplineSet.add("Meme Coins")
26 val topic = realm.copyToRealm(Topic())
27 topic.name = "Bitcoin Mining and Climate Change"
28 val discussionThemes = topic.discussionThemes
29 // Add a list of themes
30 discussionThemes.addAll(listOf("Memes", "Blockchain", "Cloud Computing",
31 "SNL", "Weather Disasters from Climate Change"))
32}
33
34// find experts for a discussion topic and add them to the panelists list
35val experts: RealmResults<Expert> = realm.where<Expert>().findAll()
36val topic = realm.where<Topic>()
37 .equalTo("name", "Bitcoin Mining and Climate Change")
38 .findFirst()!!
39topic.discussionThemes.forEach { theme ->
40 experts.forEach { expert ->
41 if (expert.disciplines.contains(theme)) {
42 topic.panelists.add(expert)
43 }
44 }
45}
46
47//observe the discussion themes set for any changes in the set
48val discussionTopic = realm.where<Topic>()
49 .equalTo("name", "Bitcoin Mining and Climate Change")
50 .findFirst()
51val anotherDiscussionThemes = discussionTopic?.discussionThemes
52val changeListener = SetChangeListener { collection: RealmSet<String>,
53 changeSet: SetChangeSet ->
54 Log.v(
55 "EXAMPLE",
56 "New discussion themes has been added: ${changeSet.numberOfInsertions}"
57 )
58}
59
60// Observe set notifications.
61anotherDiscussionThemes?.addChangeListener(changeListener)
62
63// Techno King is no longer into Meme Coins - remove the discipline
64realm.executeTransaction {
65 it.where<Expert>()
66 .equalTo("name", "Techno King")
67 .findFirst()?.let { expert ->
68 expert.disciplines.remove("Meme Coins")
69 }
70}

#UUIDs

The Realm SDKs also now support the ability to generate and persist Universally Unique Identifiers (UUIDs) natively. UUIDs are ubiquitous in app development as the most common type used for primary keys. As a 128-bit value, they have become the default for distributed storage of data in mobile to cloud application architectures - making collisions unheard of.

Previously, Realm developers would generate a UUID and then cast it as a string to store in Realm. But we saw an opportunity to eliminate repetitive code, and with the release of UUID data types, Realm comes one step closer to boilerplate-free code.

Like with the other new data types, the release of UUIDs also brings Realm's data types to parity with MongoDB. Now mobile application developers will be able to set UUIDs on both ends of their distributed datastore, and can rely on Realm Sync to perform the replication.

1import io.realm.Realm
2import io.realm.RealmObject
3import io.realm.annotations.PrimaryKey
4import io.realm.annotations.RealmField
5import java.util.UUID;
6import io.realm.kotlin.where
7
8open class Task: RealmObject() {
9 @PrimaryKey
10 @RealmField("_id")
11 var id: UUID = UUID.randomUUID()
12 var name: String = ""
13 var owner: String= ""
14}
15
16realm.executeTransaction { r: Realm ->
17 // UUID field is generated automatically in the class constructor
18 val newTask = r.copyToRealm(Task())
19 newTask.name = "Update to use new Data Types"
20 newTask.owner = "Realm Developer"
21}
22
23val taskUUID: Task? = realm.where<Task>()
24 .equalTo("_id", "38400000-8cf0-11bd-b23e-10b96e4ef00d")
25 .findFirst()

#Conclusion

From the beginning, Realm's engineering team has believed that the best line of code is the one a developer doesn't need to write. With the release of these unique types for mobile developers, we're eliminating the workarounds – the boilerplate code and negative impact on CPU and memory – that are commonly required with certain data structures. And we're doing it in a way that's idiomatic to the platform you're building on.

By making it simple to query, store, and sync your data, all in the format you need, we hope we've made it easier for you to focus on building your next great app.

Stay tuned by following @realm on Twitter.

Want to Ask a Question? Visit our Forums.

Want to be notified about upcoming Realm events, like talks on SwiftUI Best Practices or our new Kotlin Multiplatform SDK? Visit our Global Community Page.

Rate this article
MongoDB logo
© 2021 MongoDB, Inc.

About

  • Careers
  • Investor Relations
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2021 MongoDB, Inc.