I’m working with my team on an update to a How-To article about using Realm with SwiftUI, and I have some feedback for the Realm developers.
What you have done looks great and works nicely and there are a few things I would love to see added that I’d argue would make it even better.
Modifying a realm object from a subview
Regarding the @ObservedResults
wrapper as explained in the Use Realm Database with SwiftUI — MongoDB Realm quick start, this is very nice. The move, append, and remove (atOffsets: IndexSet) integrate very nicely with the ForEach. However, then when we want to do a write to one of the objects from a child View, it gets pretty awkward.
For example, let’s say you want to implement setting the favorite flag to true on the item. The QuickStart uses a Toggle with a $item.favorite binding, but I wanted to use a Button. I tried to take the action to set the favorite attribute to true, and then I ran into the whole series of Realm issues that naive people like me generally hit:
- The object was frozen, so you need to thaw it first (granted, this is a tip in the docs)
- I had to use the right Realm instance to update the object. For some reason, let realm = try Realm() did NOT give me the correct (local only) Realm instance that I had created the object with in the parent view (!) so I ended up with code that looked like this:
if let writableItem = item.thaw() {
try? writableItem.realm?.write {
writableItem.favorite.toggle()
}
}
Which worked although it looks a mess, but then I realized that @ObservedRealmObject
has a wrappedValue so I tried this:
$item.favorite.wrappedValue = true
Which also worked but looks way better. Is this what you’d recommend that we should do to update an @ObservedRealmObject
in general? If not, please correct me…if so, this may be a nice add to the documentation.
Getting the right realm instance
For all of my own applications, I only ever have one Realm instance, whether I’m using Sync with Atlas, or not. I’ve never had a reason to use multiple Realms. However, since I started using Realm SwiftUI, I am frequently getting an error that I’m using the wrong Realm instance for the object I’m trying to modify. The thing is that with this API the instance I’m using is created under the hood in some cases and I haven’t the foggiest how to reference it. The exception to this is using AsyncOpen because with that you explicitly handle
switch asyncOpen {
case .open(let realm) {
.
.
.
So then you can inject this into @Environment
or wherever you want to keep it.
Conversely, when you don’t use sync and you start with @ObservedResults
and @ObservedRealmObject
for your data model and then you write something as try? Realm.write { realm.updateThing }, you get an error that the realm instance must match that with which you created the object. But how do I get that instance?
I just want a singleton Realm, please.
Deleting a Realm object from a list
The List swipe-delete thing to delete an Item is fine, but I want a button that deletes the item. Now it gets fun…
I looked at how this is done in the RChat when you have the ChatInputBox that updates the list of ChatMessages in the ChatBalloons, and what was done there is to write a function in the parent view to (in this case add) or delete a message from the list of messages, and it passes that closure to the child View. Is that really the easiest way to do this? Do I have to have a binding to the containing list in order to remove an item? What about if there was a method on the LinkingObjects that had a signature like linkingObjects.remove(item)?
So I write my closure in the parent view, pass it down, call back up from the child view button with my item argument, then I want to call
$group.items.remove(item)
…but I can’t because that method signature isn’t supported. After a bit of deliberation, I land on this:
for (index, anItem) in group.items.enumerated() where item._id == anItem._id {
$group.items.remove(at: index)
}
Which works, but looks a mess. Please tell me there is an easier way! If not, please plan an easier way on the roadmap. I’ll file a ticket if you like.