realm-swift icon indicating copy to clipboard operation
realm-swift copied to clipboard

@ObservedRealmObject doesn't update for changes in EmbeddedObject

Open jeanbaptistebeau opened this issue 1 year ago • 14 comments

@ObservedRealmObject doesn't update when you change properties in an EmbeddedObject property. Is there any way to make that happen?

jeanbaptistebeau avatar Mar 29 '23 08:03 jeanbaptistebeau

Since @ObservedRealmObject can't be used for embedded objects, and given the nature of embedded objects, it would seem more logical to me that changes in embedded objects are propagated to the parent object. Is there a specific reason not to have this behavior?

jeanbaptistebeau avatar Mar 29 '23 09:03 jeanbaptistebeau

@jeanbaptistebeau @ObservedRealmObject can be used with EmbeddedObjects and it will update the View on modification. Can you show some snippets of your code?

See example here

leemaguire avatar Mar 29 '23 09:03 leemaguire

@leemaguire Here's a minimal example:

import SwiftUI
import RealmSwift


class Parent: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var _id: String = UUID().uuidString
    @Persisted var name: String = ""
    @Persisted var child: Child?
}

class Child: EmbeddedObject {
    @Persisted var name: String = ""
}


@main
struct SwiftUITestsApp: App {
    var body: some Scene {
        WindowGroup {
            RealmObserveTestView()
        }
    }
}


struct RealmObserveTestView: View {
    
    @State private var update = 0
    
    var body: some View {
        innerView
            .onAppear {
                let realm = try! Realm()
                
                if realm.objects(Parent.self).isEmpty {
                    try! realm.write {
                        realm.add(Parent())
                    }
                    
                    update += 1
                }
            }
    }
    
    @ViewBuilder
    var innerView: some View {
        if let parent = try! Realm().objects(Parent.self).first {
            RealmObserveTestInnerView(parent: parent)
        }
    }

}

struct RealmObserveTestInnerView: View {
    
    @ObservedRealmObject var parent: Parent

    var body: some View {
        VStack(spacing: 40) {
            Text("Parent name: \(parent.name)")
            Button("Update") {
                guard let parent = parent.thaw() else { return }
                guard let realm = parent.realm else { return }
                
                try! realm.write {
                    parent.name = parent.name + "n"
                }

            }
            
            Text("Child name: \(parent.child?.name ?? "nil")")
            Button("Update") {
                guard let parent = parent.thaw() else { return }
                guard let realm = parent.realm else { return }

                try! realm.write {
                    parent.child?.name = (parent.child?.name ?? "") + "n"
                }
            }
        }
    }
}

(You may have to restart app once). Parent name is updating properly but not child name.

Using version 10.32.3

jeanbaptistebeau avatar Mar 29 '23 10:03 jeanbaptistebeau

@jeanbaptistebeau thanks for this, I see what you mean. Currently the change notifications are shallow, meaning any changes on a linked object (either EmbeddedObject or Object) won't trigger a change notification unless the key paths for the linked objects are specified. We currently don't do that in @ObservedRealmObject and that's why this issue is occurring. We could expose the ability to pass in key paths for the change listener which would solve this issue. I will discuss with the team.

leemaguire avatar Mar 30 '23 08:03 leemaguire

Thanks for the explanation. That feature would be great. It's because of small details like this that I often have to create my own property wrappers and can't use the provided ones :)

jeanbaptistebeau avatar Apr 03 '23 17:04 jeanbaptistebeau

A heavy second for that - this would be game-changing and simplify architecture substantially

aehlke avatar Apr 19 '23 20:04 aehlke

I'll add a common use case of mine: I have a SwiftUI view with a list of objects on left, and a form for an object on the right. The user can update the title or other properties of the object in the form, and expects to see the updated title reflected in the sidebar etc. As a developer, the convenience of ObservedRealmObject is made redundant when I have to also then add a view model class with a publisher subscription just to get the view to listen to updates to the object.

It'd be great to have some visibility into SwiftUI plans because there are some gaps like this that make it challenging to do much quickly and I think the demo projects from Realm reflect this in the simplicity of what is done with SwiftUI. Thanks very much

aehlke avatar May 03 '23 17:05 aehlke

Have the exact same situation as @aehlke. To be honest, it is a bit puzzling how this is not implemented yet.

chernushevich avatar Jun 30 '23 14:06 chernushevich

Thanks for acknowleding this @leemaguire and @jeanbaptistebeau for raising it. Have you found any nice patterns from adding observers in a ViewModel? This is almost a deal breaker for us in the usecase at the early stage in a project.

EmbeddedObjects are a really nice way of forming a hierarchy in an object and I hadn't used it in my previous work with Realm last year, so it was a delight to discover and a shame to run into this issue.

kelvinharron avatar Oct 23 '23 19:10 kelvinharron

@jeanbaptistebeau thanks for this, I see what you mean. Currently the change notifications are shallow, meaning any changes on a linked object (either EmbeddedObject or Object) won't trigger a change notification unless the key paths for the linked objects are specified. We currently don't do that in @ObservedRealmObject and that's why this issue is occurring. We could expose the ability to pass in key paths for the change listener which would solve this issue. I will discuss with the team.

Is there some plan to resolve this? Since this issue is not proactively explained to those who use Realm in SwiftUI, I have had to spend a lot of time trying to figure out whether I was doing something wrong and then resorting to sending manual notifications to trigger an update I'd otherwise expected Realm to make...

HKdAlex avatar Feb 10 '24 20:02 HKdAlex

Another vote for an update on this. I'm in a similar situation to @aehlke in that I'm using iPad sidebars. Lets say I have a concept of folders that can each have many notes; if I edit a note, the view for the folder does not update until I manually trigger a redraw which removes the convenience of @ObservedRealmObject.

How is everyone else working around this for the time being?

bendodson avatar Mar 07 '24 16:03 bendodson

@bendodson i ended up creating a custom version of the @ObservedRealmObject property wrapper, by essentially just copying the realm source code for ObservedRealmObject and some of its helper types, and changing the init(wrappedValue:) initializers to also pass an array of key paths to the ObservableStorage. (For some reason, the ObservableStorage does in fact already have the ability to observe specific key paths, but this is currently unused.)

For the observed key paths, I specify all properties of the object itself, plus all properties of all EmbeddedObject members of the object. The code for this is based on what I shared here a while ago, with some changes to also support EmbeddedObject properties. (Namely, the as! RealmSwift.Object.Type cast is instead an as! RLMObjectBase.Type cast.)

(Edit: Having had a second look at the function i posted in the linked post, i just noticed that it's an old version that contained a bug: the filter condition in the last line should of course be !propertyNamesToIgnore.contains(name) && !propertyNamesToIgnore.contains { $0.starts(with: "\(name).") })

lukaskollmer avatar Mar 22 '24 15:03 lukaskollmer

@lukaskollmer Could you perhaps send your custom property wrapper to the Realm team as a PR? Otherwise, this seems like yet another one of those Realm issues that's going to linger for a decade with no progress.

bdkjones avatar Jun 05 '24 07:06 bdkjones

How is everyone else working around this for the time being?

lukaskollmer's solution sounds interesting

personally I replaced most usage of the swiftui wrappers with lots of combine publishers on realms and objects

aehlke avatar Jun 05 '24 17:06 aehlke