MongoDB Realm and SwiftUI @ObservedResults

Richard Krueger
Realm Blog
Published in
6 min readApr 22, 2021

--

I have been developing for MongoDB Realm since its release in June 2020. In my opinion, this system is the only real-time database to support truly collaborative computing that is simultaneously scalable, secure, and supports offline-first capabilities.

When MongoDB Realm was initially released, it provided little to no integration with Apple’s new SwiftUI technology. This is not to say that you could not develop a SwiftUI application using Realm, only that there was no easy way to embed Realm within a SwiftUI view directly using property wrappers. In fact back in June of 2020, I wrote an article on how to build a simple chat application in SwiftUI using MongoDB Realm without this level of support.

Thankfully, the MongoDB Realm team has been extremely busy improving their technology, and have greatly simplified the task of writing a SwiftUI/Realm application. This effort has been lead by Jason Flax who has done an excellent job integrating MongoDB Realm with SwiftUI by introducing a number of new Realm specific property wrappers.

About two years ago, Apple unveiled a number of property wrappers for SwiftUI as part of its Combine framework for quickly building data-driven UIs. For a more detailed explanation of this technology, I would highly recommend Mark Moeykens’ new book Working with Data in SwiftUI. This is a relatively easy read, chocked with visual examples, that covers how a developer can easily build a Swift data driven UI. Explaining the details of Apple’s new property wrapper technology is beyond the scope of this article. Mark Moeykens has a unique style that is direct and to the point; he is the living counterpoint to Matt Neuburg, who authors thousand plus page compendiums (that although accurate) are guaranteed to put you to sleep.

The MongoDB Realm iOS team has recently created a number of Realm specific property wrappers that provide a mechanism for hydrating a SwiftUI view directly from a Realm live object query. The result is that any change in the cloud data base is automatically reflected in real-time within the running SwiftUI application, with little to no additional code. For more information on this new Realm offering, I would highly recommend the following video, which was aired at a Realm Meetup in February 2021 called SwiftUI Best Practices with Realm. What Jason Flax’s engineering team achieved is nothing less than a major breakthrough UI notation, not unlike what Alan Kay pulled off at Xerox Park back in the early 1980s with SmallTalk. The code simplifications that are accomplished with this technology are simply astounding.

To date, the documentation surrounding the new SwiftUI Realm extensions is somewhat meager. This is not to be wholly unexpected, since documentation usually lags engineering by several months. The official MongoDB Realm documentation that tangentially covers the new SwiftUI Realm property wrappers can be found in the section on Use Realm Database with SwiftUI and Combine. Fortunately, one of Realm’s Developer Advocates — Andrew Morgan — has done an excellent job proselytizing this new Realm technology with a combination of a working MongoDB Realm Chat Application, videos, and Medium articles. Again, Andrew covers these new SwiftUI Realm features extensively in his video called Realm Sync in use — Building and Architecting a Mobile Chat App.

Andrew has developed a full featured open source Chat app for Realm called RChat, which makes extensive use of all the new SwiftUI Realm extensions. This is a multi-user chat program written for Realm. Since it is open source under the Apache license, portions of it could be used by any developer who would need to implement chat functionality within their application. It was by combing through his code (and accompanying videos), that I was able to gain a true understanding of the new SwiftUI Realm property wrappers. The GitHub repo for Andrew’s RChat application can be found here. This chat program is perhaps the best working example of MongoDB Realm’s capabilities.

Since the 1980s, I have always been fascinated by two types of programs: Paint and Chat. I was the author of a natural media paint system for Windows called Fauve Matisse, and the author of a high resolution image editor called Fauve xRes; my brother and I (who were the founders of Fauve) subsequently sold our company to Macromedia is 1995. Fauve Matisse had about half a million users back in the day. The technology eventually found its way into Adobe Fireworks and Apple’s Final Cut video editor (which started out at Macromedia). Chat has always held a special place in my heart because it is the foundational technology to collaborative computing.

If Andrew Morgan could write a full featured chat program for Realm, I decided that it would be a challenge to write the world’s smallest cloud hosted chat program using the same technology. The exercise was born more out of intellectual curiosity than anything else. The result from my two day hiatus was an application called TinyChat, for which the GitHub repository is presented here.

TinyChat has a very simple user interface. It works entirely on anonymous logins, where the user enters a nickname and then transitions to a shared chat thread as shown below

Anonymous Login Screen

Once the user has logged in by pressing the Chat button, the UI transitions to the following shared chat view.

Chat View

This entire program is 74 lines of SwiftUI code, along with three lines of MongoDB Realm integration code! Come again?

Christopher Walken in True Romance

First, we write one line of code that ties the Swift program to the MongoDB Realm application as follows:

let app: RealmSwift.App? = RealmSwift.App(id: "tinychat-vwhsb")

Next, we open a Realm configuration on the “chat” partition and store it in an environment, within a simple containing ChatView.

struct ChatView: View {    @Binding var name: String    var body: some View {
ChatViewContent(name: $name).environment(
\.realmConfiguration,
app!.currentUser!.configuration(partitionValue: "chat"))
}
}

In the ChatViewContent view, we define an @ObservedResults() Realm property wrapper that defines a query on the chat Realm passed in through the environment from ChatView as follows

@ObservedResults(ChatEntry.self, sortDescriptor: 
SortDescriptor(keyPath: "createdAt", ascending: true))
var chatEntries

This chatEntries Results object is a live object that acts very much like a SwiftUI @State variable, in that any changes to the queried result will automatically result in a redraw of the view.

The Chat log itself is stored within a SwiftUI ScrollView defined in a LazyVStack.

ScrollView {
ScrollViewReader { proxy in
LazyVStack(alignment: .leading, spacing: 5) {
ForEach(chatEntries) { chatEntry in
VStack(alignment: .leading) {
Text(chatEntry.name)
.foregroundColor(.gray)
.font(Font.caption)
.padding(.bottom)
Text(chatEntry.text).font(Font.title)
}
.id(chatEntry._id)
.padding()
}
}
.onAppear() {
scrollToBottom(proxy: proxy)
}
.onChange(of: chatEntries.count) { _ in
scrollToBottom(proxy: proxy)
}
}
}
}

The scrollToBottom function takes place on any new entries added to the chatEntries Results variable, or when the view appear for the first time. This code is extremely simple based on the proxy passed in through the ScrollViewReader.

func scrollToBottom(proxy: ScrollViewProxy) -> Void {
if let last = chatEntries.last {
proxy.scrollTo(last._id, anchor: .bottom)
}
}

Adding, new entries to the Chat thread is handled through a simple SwiftUI TextField.

HStack(spacing: 10) {
TextField("Type a message", text: $chatText, onCommit: {
self.addChatMessage()
})
.padding(10)
.overlay(
// Add the outline
RoundedRectangle(cornerRadius: 8)
.stroke(Color.blue, lineWidth: 2)
)
Button(action: {
self.addChatMessage()
}) {
Image(systemName: "arrow.up.circle.fill")
}
.font(.largeTitle)
}
.padding()

Finally, the addChatMessage() function can leverage the chatEntries variable by simply appending the latest chat message to it.

func addChatMessage() -> Void {
let chatEntry = ChatEntry(name: self.name, text:self.chatText)
$chatEntries.append(chatEntry)
self.chatText = ""
}

Lastly, the database scheme itself is specified directly within the Swift code as a simple Realm class. For these new Combine Realm property managers to work, the developer must subclass the ChatEntry class off of ObjectKeyIdentifiable.

class ChatEntry: Object, ObjectKeyIdentifiable {
@objc dynamic var _id = ObjectId.generate()
@objc dynamic var name = ""
@objc dynamic var text = ""
@objc dynamic var createdAt: Date? = nil
override static func primaryKey() -> String? {
return "_id"
}
override static func indexedProperties() -> [String] {
return ["createdAt"]
}
convenience init(name: String, text: String) {
self.init()
self.name = name
self.text = text
self.createdAt = Date()
}
}

And the rest gentlemen is history. Happy Realming.

I am currently working on a startup company called Cosync, that specializes in JWT authentication and Amazon S3 Storage integration with MongoDB Realm.

--

--

Richard Krueger
Realm Blog

I have been a tech raconteur and software programmer for the past 25 years and an iOS enthusiast for the last eight years. I am the founder of Cosync, Inc.