Realm Swift(UI) pain points

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:

  1. The object was frozen, so you need to thaw it first (granted, this is a tip in the docs)
  2. 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.

9 Likes

Hi @Mark_Powell1,

Thanks for the great detailed feedback. It’s very appreciated!

I started thinking about the “Deleting a Realm object from a list” point. I worked a little on it here: Remove object from a list · Issue #7663 · realm/realm-swift · GitHub.

Some other team members will comment on the other points too. Thanks again.

2 Likes

Hey @Mark_Powell1 - thank you for taking the time to explain the things you’re running into! This is great feedback. I’m one of the folks who work on the documentation for the Swift SDK; I’m glad you found some of the info you need there.

I’ll keep an eye on discussion here as I believe there may be some workarounds for some of the things you’ve mentioned. As members of the SDK team chime in with more details, I’ll be watching for things we might add to the docs to provide better info for devs who want to use Realm with SwiftUI.

I don’t think it addresses any of the things you’ve raised here, but we’ve also got a Use Realm Database with SwiftUI Guide that explains some of the things a little more explicitly that we go through quickly and may gloss over in the quick start. It’s buried a little deeper in the docs; I’ll put in a ticket next week to add a link to it from the Quick Start so it’s more visible.

Again, thank you so much for the detailed feedback, and I’m looking forward to beefing up the docs around some of these things as the SDK team provides details about possible workarounds.

2 Likes

The projected value of our property wrappers allows you to “quick write.” You should just be able to do $item.favorite = true. I would also recommend keeping your view code as small as possible to be able to use our property wrappers and the quick write functionality wherever you can.

1 Like

Looks like a great start to the ideation, thank you!

Very nice, very nice. Hard to imagine that getting simpler. Thanks for validating my thinking, that is helpful!

Thanks so much Dachary! These docs have really been improved a lot lately, and I made it a point to mention that to other folks on the MongoDB Realm team recently…in particular, partitioning strategy got some documentation love that really helped us understand it better.

Not sure if I just missed it before, but I see that $item.favorite = true quick write technique explicitly called out in the SwiftUI guide you linked to, so that’s there!

I think maybe there what I’m after with the realm instance is sort of already there, just maybe I need an example implementation for my use case? Specifically, there is .environment(.realm, realm) to inject an opened realm into a view…I think I would just need to make my realm instance at the top view and inject it there into all the children, if that’s possible? That seems do-able for the asyncOpen (gives an opened realm instance back) use case just fine, and if there’s a way to do that for a local offline realm instance as well, I think that might do it.

1 Like

There is a .environment(.realmConfiguration, …) that you can pass any configuration (local included) into :grin:.

1 Like

Right…the docs say that the ObservedResults, ObservedRealmObject implicitly create a realm instance. If you pass in a realm instance via environment, that takes precedence? If you make a local realm instance at the App level and inject it into the environment there, is that the instance that will be reused by all the child views?

Ok, so, here is my problem with this now.
I took the Realm SwiftUI quick start verbatim and added only this bit of code here:

           Button {
                item.isFavorite.toggle()
            } label: {
                Text("Toggle Fave")
            }

…to the ItemDetailsView. When you tap the Button, you get this:

Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to modify a frozen object - call thaw on the Object instance first.'

However, when you call this:

                $item.isFavorite.wrappedValue.toggle()

It seems to work just great for me. Is this a bug, or is this how we’re supposed to code it?

1 Like

This gives me a compile error:

Cannot assign through dynamic lookup property: '$item' is immutable
Cannot assign value of type 'Bool' to type 'Binding<Bool>'

When item is an @\ObservedRealmObject as in the Realm SwiftUI quick start ItemDetailsView.

1 Like

item.isFavorite.toggle() will not work because you need to call the projected value (using the dollar sign). Using the $ will effectively open a write transaction, enabling the set behaviour previously mentioned.

Is $item.isFavorite.wrappedValue.toggle() not compiling?

Is $item.isFavorite.wrappedValue.toggle() not compiling?
Yes this compiles and seems to work just fine! Is this way what you’d recommend?

I ask because I thought you wrote earlier:

$item.favorite = true

but this does not compile for me, and I took it as something of a recommendation so I then wondered whether it’s a bug, etc.

This is what I’m searching for and was hoping to find but was not answered in this thread:

Once a user logs in, I want every interaction with Realm to be the same realm instance with a single _partition defined in the models.

I discussed my issues more here, but I would love to just understand if there is a clean way to set one realm and always use it. If there isn’t, then what in the world do we do to actually make sure we’re using the same realm.

I feel like I’m taking crazy pills, and thanks @Mark_Powell1 for this thread because it seems like I’m not the only one. Any complexity beyond the examples and there seems to be a bunch of unwritten workarounds to get it to work right. At the very least, I just wish they were written somewhere.

As beginner on Realm/Swift (but with some background on web frameworks) I must say Realm is great on early stages.
But it lacks on trivial operations (present on web frameworks ) when you go a step further.

You guys really should consider better DX for trivial operations, reported here and many other issues. It does not need to be or sound like a ORM, but a rich API/SDK means happy developer.

Hi @Robson_Tenorio, welcome to the community.

The original post on this thread was concerning some SwiftUI issues and since the time of the post, the documentation has been updated to add some clarity to several of the topics. Some new features were also added to the SDK along way too.

The Realm team is always open to feature requests and feedback if something is unclear or undocumented.

Perhaps you can create a new thread with some of the shortcomings you’re running into so we can explore those a bit. Can you maybe come up with some example code we can take a look at? That would be really beneficial to the development team and will add details to help make the API/SDK richer!