I’m running into what look like threading issues with Realm. I’m trying to filter a Results list of objects by another object that has a many to many relationship. I’m using a singleton RealmManager, initialized on startup, getting a user account (which is separate from a Realm.user) with that realm, and then trying filter @ObservedResults with that account object in a computed property.
I’m getting a runtime crash, “Object must be from the Realm being queried.” This error is confusing since I’m only creating one Realm, and the Realm initialization code is tagged with @MainActor. What is going on? Is @ObservedResults returning on a different thread or on a different Realm?
Also, as a side note, I cobbled this solution together from some outdated examples and pretty sparse documentation, so I’m sure I’m doing a lot wrong. Any other suggestions welcome. Thanks in advance for your help.
The code that is crashing:
struct MapView: View {
@EnvironmentObject var user: UserAccount
@ObservedResults(Beacon.self) var beacons: Results<Beacon>
@MainActor
var userBeacons: Results<Beacon> {
beacons.filter("%@ IN participants", user.account!) // Crashes here
}
...
RealmManager:
class RealmManager: ObservableObject {
let appId = "beacon1-iuaze"
@Published var realm: Realm?
static let shared = RealmManager()
@MainActor
func initialize() async throws {
let app = App(id: appId)
let user = try await app.login(credentials: Credentials.anonymous)
var config = user.flexibleSyncConfiguration(initialSubscriptions: { subs in
if subs.first(named: "all-accounts") == nil {
subs.append(QuerySubscription<Account>(name: "all-accounts"))
}
if subs.first(named: "all-messages") == nil {
subs.append(QuerySubscription<Message>(name: "all-messages"))
}
if subs.first(named: "beacons") == nil {
subs.append(QuerySubscription<Beacon>(name: "beacons"))
}
}, rerunOnOpen: true)
// Pass object types to the Flexible Sync configuration
// as a temporary workaround for not being able to add complete schema
// for a Flexible Sync app
config.objectTypes = [Account.self, Beacon.self, Message.self]
realm = try await Realm(configuration: config, downloadBeforeOpen: .always)
}
}
Initialization
@main
struct MyApp: SwiftUI.App {
@StateObject private var realmManager = RealmManager.shared
@StateObject private var locationManager = LocationManager.shared
var body: some Scene {
WindowGroup {
VStack {
if let realm = realmManager.realm {
MainView(user: UserAccount(realm: realm))
.environmentObject(realmManager)
.environmentObject(locationManager)
}
}.task {
try? await realmManager.initialize()
}
}
}
}
Account
class Account: Object, ObjectKeyIdentifiable {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var name: String
@Persisted var phone: String
@Persisted var email: String
@Persisted(originProperty: "participants") var beacons: LinkingObjects<Beacon>
convenience init(name: String, phone: String, email: String) {
self.init()
self.name = name
self.phone = phone
self.email = email
}
}
UserAccount
class UserAccount: ObservableObject {
private static let EMAIL_KEY = "email"
@Published var account: Account?
convenience init(realm: Realm) {
self.init()
self.account = UserAccount.getAccountFromDefaults(realm: realm)
}
func setAccount(account: Account) {
let defaults = UserDefaults.standard
defaults.set(account.email, forKey: UserAccount.EMAIL_KEY)
self.account = account
}
static func getAccountFromDefaults(realm: Realm?) -> Account? {
let defaults = UserDefaults.standard
guard let email = defaults.string(forKey: UserAccount.EMAIL_KEY) else {
return nil
}
let query = realm!.objects(Account.self).where {
$0.email == email
}
if query.count > 0 {
return query.first
} else {
defaults.set("", forKey: UserAccount.EMAIL_KEY)
}
return nil
}
}
MainView
struct MainView: View {
@EnvironmentObject var errorHandler: ErrorHandler
@StateObject var user: UserAccount
var body: some View {
if user.account != nil {
MapView()
.environmentObject(user)
} else {
AccountView()
.environmentObject(user)
}
}
}