ios – @Published Realm object not triggering view redrawing in SwiftUI

0
114


In our app we use a UserService that is a ObservableObject and passed as environment. A synced realm is opened and the app user (a RealmObject) is obtained using flexible sync.

When updating the users properties, such as his username, the view does not get redrawn. This is against my expectations since UserService contains a @Published property where the user (that is being edited) is stored. On the database it clearly shows the property being edited, however the view does not get redrawn, only when restarting the app the new properties are shown.

What would be the best way to have a UserService objects taking care of all user related logic (storing a user object (reference to it), containing functions to update, …) and use this to display the active data of this user throughout the views?

Here is a MRE (the login logic is left out to reduce complexity):

import SwiftUI
import RealmSwift

class UserService2: ObservableObject {
    
    var realm: Realm
    @Published var ownUser: User
    
    init(realm: Realm, ownUser: User) {
        self.realm = realm
        self.ownUser = ownUser
    }
    
    func changeName(newName: String) {
        do {
            try self.realm.write {
                self.ownUser.userName = newName
            }
        } catch {
            print("error")
        }
    }
}
struct TestView: View {
    
    @EnvironmentObject var userService: UserService2
    
    var body: some View {
        VStack {
            Text(userService.ownUser.userName ?? "no name")
            Button {
                userService.changeName(newName: Date().description)
            } label: {
                Text("change name")
            }
        }
    }
}

struct ContentView: View {
    
    var realm: Realm? = nil
    
    init() {
        let flexSyncConfig = app.currentUser!.flexibleSyncConfiguration(initialSubscriptions: { subs in
            subs.append(
                QuerySubscription<User>(name: "ownUserQuery") {
                    $0._id == "123"
                })
        })
        do {
            let realm = try Realm(configuration: flexSyncConfig)
            self.realm = realm
        } catch {
            print("sth went wrong")
        }
    }
    
    var body: some View {
        if let realm = realm, let ownUser = realm.objects(User.self).where( { $0._id == "123" } ).first {
            TestView()
                .environmentObject(UserService2(realm: realm, ownUser: ownUser))
        } else {
            ProgressView()
        }
    }
}

The User Object looks like this

import Foundation
import RealmSwift

class User: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var _id = UUID().uuidString
    @Persisted var userName: String?
    
    convenience init(_id: String? = nil, userName: String? = nil) {
        self.init()
        if let _id = _id {
            self._id = _id
        }
        self.userName = userName
    }
}

P.S. I assume I could observe changes on the object using realm and somehow force a view refresh, but I would find it much more clean using the already existing way to watch for changes and redraw views when needed using @Published

P.P.S. This user object is created on the server using a trigger when someone authenticates. However, I assume this is not really relevant to this problem.