Single document per user

If there is a single document of some type per user, such as UserPreferences, I could setup a trigger when a user creates an account. However, I am not sure what is the best/cleanest way to approach the problem when Realm is used locally.

It would be nice if we could query for a single result using @ObservedResult(UserPreferences.self) var userPreferences = defaultValue similar to @AppStorage.

Is it possible to abstract the logic in a view model (or a property wrapper)? I can’t use lazy and @ObservedRealmObject at the same time, but lazy is needed to avoid Cannot use instance member 'getUserPreferences' within property initializer.

import Foundation
import RealmSwift

class ViewModel: ObservableObject {
    @ObservedResults(UserPreferences.self) private var _userPreferences
    
    // @ObservedRealmObject lazy var userPreferences: UserPreferences = getUserPreferences()
        
    private func getUserPreferences() -> UserPreferences {
        if let userPreferences = _userPreferences.first {
            return userPreferences
        }
        let newUserPreferences = UserPreferences()
        $_userPreferences.append(newUserPreferences)
        return newUserPreferences
    }
}

If I try to use environment objects, I get Thread 1: "Frozen Realms do not change and do not have change notifications.".

import RealmSwift
import SwiftUI

class UserPreferences: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var isDarkTheme: Bool
}

struct WrapperView: View {
    @ObservedResults(UserPreferences.self) private var _userPreferences
    
    var body: some View {
        if let userPreferences = _userPreferences.first {
            MainScreen()
                .environmentObject(userPreferences)
//            MainScreen(userPreferences: userPreferences)
        } else {
            ZStack {
                // Empty view
            }
            .onAppear{
                $_userPreferences.append(UserPreferences())
            }
        }
    }
}

struct MainScreen: View {
//    @ObservedRealmObject var userPreferences: UserPreferences

    @EnvironmentObject var userPreferences: UserPreferences
    
    var body: some View {
        VStack{
            Button("Toggle") {
                userPreferences.isDarkTheme.toggle()
            }
            Text(userPreferences.isDarkTheme.description)
        }
    }
}

Also, when using triggers, there is a small chance that there is a connection error after logging in but before opening the realm.

    // from template code
    @AsyncOpen(appId: "appId", timeout: 4000) var asyncOpen
        case .waitingForUser:
            ProgressView("Waiting for user to log in...")
            // The realm has been opened and is ready for use.
            // Show the content view.
        case .open(let realm):
            ItemsView(itemGroup: {
                if realm.objects(ItemGroup.self).count == 0 {
                    try! realm.write {
                        // Because we're using `ownerId` as the queryable field, we must
                        // set the `ownerId` to equal the `user.id` when creating the object
                        realm.add(ItemGroup(value: ["ownerId":user!.id]))
                    }
                }
                return realm.objects(ItemGroup.self).first!
            }(), leadingBarButton: AnyView(LogoutButton())).environment(\.realm, realm)

If we rely on having a single ItemGroup, the app wouldn’t work (when waiting for a trigger to execute).

If we create the single ItemGroup locally, then there is a chance we create a second one if the user already exists but opening the synced realm failed (logging in for the first time on a different device where the realm is empty). In that case, I think let itemGroup = itemGroups.first will return the oldest item group. It would be convenient to have a @ObservedResult that merges changes in case this happens.