How do you change the default configuration for @ObservedResults()?

According to the example here

you simply pass in the Realm.Configuration to the View like so

AnyView ().environment(\.realmConfiguration, Realm.Configuration(...))

And within the View create a variable to access the realm objects
@ObservedResults(Object.Self) var objects

should use the realm configuration being passed in but this does not seem to work.

I have the following:

extension Realm {

static var IAMRealm: Realm? {

let configuration = Realm.defaultConfig

do {

let realm = try Realm(configuration: configuration)

return realm

} catch {

os_log(.error, "Error opening realm: \(error.localizedDescription)")

return nil

}

}

**static** **var** defaultConfig: Realm.Configuration {

**return** Realm.Configuration(schemaVersion: 1)

}

**static** **func** setDefaultConfig(){

Realm.Configuration.defaultConfiguration = Realm.defaultConfig

}

}

**private** **struct** RealmConfigurationKey: EnvironmentKey {

**static** **let** defaultValue = Realm.defaultConfig

}

**extension** EnvironmentValues {

**var** realmConfiguration: Realm.Configuration {

**get** { **self** [RealmConfigurationKey. **self** ] }

**set** { **self** [RealmConfigurationKey. **self** ] = newValue }

}

}

The only way I could get it to work was by calling the following from the SwiftUI.App prior to returning the main window.

@main
struct RealmApp: SwiftUI.App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
       initialise()
        return WindowGroup {
            ContentView()
        }
        .windowStyle(HiddenTitleBarWindowStyle())
        .windowToolbarStyle(UnifiedCompactWindowToolbarStyle(showsTitle: true))
    }
    func initialise() {
        Realm.Configuration.defaultConfiguration = Realm.defaultConfig
    }
}

Am I doing something wrong when using the .environment(.realmConfiguration, …) option ?

Apologies for all the **s - how can I stop that happening when pasting in from Xcode ?

Hi @Duncan_Groenewald, you shouldn’t need to manually inject anything into the SwiftUI environment for your view to be able to access the default Realm using @ObservedResults. You can take a look at this article to see how I updated Apple’s Scrumdinger app to store data in the default Realm.

In the top-level view I include this line…

@ObservedResults(DailyScrum. self ) var scrums

and I can then work with the results:

if let scrums = scrums {
    ForEach(scrums) { scrum in
        NavigationLink(destination: DetailView(scrum: scrum)) {
            CardView(scrum: scrum)
        }
        .listRowBackground(scrum.color)
    }
}

To get rid of the ** I copy from Xcode, paste into VS Code and then copy it again to paste here.

I’ll remember the VSCode tip ! thanks,

I want to change the default realm configuration - with schema changes I need to pass in a schema version number so that any migration will be performed. Is there another way to handle schema changes ?

BTW it tries to open with a default schema version 0 when the version needs to be 1.

Sorry - read your question too quickly! I haven’t yet experimented with multiple schema versions (in my plans for the next few months), but hopefully, someone else can chip in.

You should be able to set the defaultConfiguration from your AppDelegate class. You also should not have your own EnvironmentValues extension.

How exactly are you meant to set the defaultConfiguration in AppDelegate ? SwiftUI apps don’t usually have an AppDelegate.

And how does that correspond with the example provided in the docs here https://docs.mongodb.com/realm/sdk/ios/integrations/swiftui/

where the example provided is

LocalOnlyContentView()
  .environment(\.realmConfiguration, Realm.Configuration( /* ... */ ))

No mention of needing anything in AppDelegate.

You don’t need to do anything in AppDelegate, but if you are trying to set the default Realm configuration before any Views are displayed, it would be the simplest way to do it.

Your sample

LocalOnlyContentView()
  .environment(\.realmConfiguration, Realm.Configuration( /* ... */ ))

should work. If the Realm.Configuration is not correctly being used in the LocalOnlyContentView, then that would be a bug on our end. What does LocalOnlyContentView look like?

As a side note, if your configuration is static, you can pass it into the @ObservedResults property wrapper directly:

@ObservedResults(DailyScrum.self, configuration: Realm.Configuration(...))

My view is shown below (partially). There are no other references to any realm objects. However this would not be the first time the realm is opened from the main thread.

struct CategoryBrowserView: View {
    @ObservedResults(CategoryNode.self, filter: NSPredicate(format: "parent == nil")) var topLevelCategories
    @ObservedObject var model = ModelController.shared
    @ObservedObject var fileService = FileController.shared
    
    @State private var searchTerm: String = ""
    
    let iconSize: CGFloat = 11
    
    var projectsCategory: CategoryNode? {
        return topLevelCategories.filter("name == %@", TopLevelCategoryNames.projects.rawValue).first
    }
    var eventsCategory: CategoryNode? {
        return topLevelCategories.filter("name == %@", TopLevelCategoryNames.events.rawValue).first
    }
    var locationsCategory: CategoryNode? {
        return topLevelCategories.filter("name == %@", TopLevelCategoryNames.locations.rawValue).first
    }

...
...

What are these?

What error are you getting that implies that the correct configuration isn’t being used?

The are Swift classes containing UI state information and another one containing UI data like array of thumbnail images etc. One of them has some variables holding references to some other realm objects and realm results sets.

The error is saying the file cannot be opened because the schema version 0 is less than the current schema version 1. As you can see from my initial post we are working with version 1.

I can try adding it to the @ObservableResults() constructor to see if that works.

BTW here is a simplified version of what I do to when launching any Realm App, regardless of whether it is local or synced. The docs I have seen from Realm/MongoDB don’t do a very good job of explaining this or providing working examples that include dealing with SDK upgrades, schema version changes etc… Hope someone finds this useful.

The problem is that if the SDK version is updating the database format then it will block whatever thread you are opening the Realm on the first time. So given we have no idea when this might happen it is safer to always open on a background thread the first time and once the first open has completed then either handle any errors and display to the user or continue opening the rest of the app and use Realm as normal.

struct ContentView: View {

    /// Realm initialisation
    @State var isRealmInitialised: Bool = false
    @State var initialisationMessage: String = "Loading database, please wait"
    
    var body: some View {
        NavigationView {
            
            if isRealmInitialised {
            
                // The main App screens
                SidebarPanel()
            
                MainView()
                
                AdjustmentsPanel()

            } else {

                // Screens to display while initialising Realm
                // First time we open Realm there may be long running migrations
                // So show the user something...

                // Left panel
                Text("")

                // Center panel
                VStack {
                    Spacer()
                    ProgressView()
                    Text(initialisationMessage)
                        .foregroundColor(Color.secondaryLabel)
                    Spacer()
                }.onAppear(perform: {
                    self.initialiseDatabase()
                })

                // Right panel
                Text("")
            }  
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        
    }
    
    func initialiseDatabase() {
        let startTime = Date()
        
        Realm.asyncInitialise(completion: {result, message in

            // Show for a minimum of 1 second or things look messy
            let elapsedTime = Date().timeIntervalSince(startTime)
            let delay = max(0, 1.0 - elapsedTime)

            DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
                self.isInitialised = result
                self.initialisationMessage = message
            }
        })
    }
}

Realm extension to provide a custom async initialisation. Note that asyncOpen() will BLOCK indefinitely if there is no network connection since it will want to download the initial database before returning. As a consequence in general this should only be used the first time the user is logging in to the Cloud Realm.

extension Realm {
    static var IAMRealm: Realm? {
        let configuration = Realm.defaultConfig
        do {
            let realm = try Realm(configuration: configuration)
            return realm
        } catch {
            os_log(.error, "Error opening realm: \(error.localizedDescription)")
            return nil
        }
    }
    // Set this if there are database changes and a new version is required
    static let schemaVersion: UInt64 = 1
    
    static var defaultConfig: Realm.Configuration {
        
        var config = Realm.Configuration(schemaVersion: Realm.schemaVersion)
        
        // Code to perform any required migration
        // Note this will not be called for Synced Realms
        config.migrationBlock = { migration, oldSchemaVersion in
            
            if oldSchemaVersion < Realm.schemaVersion {
                
                os_log("Realm migration from version \(oldSchemaVersion) to \(Realm.schemaVersion)")
                
            }
            return
        }
            
        return config
    }
    /// Sets the global default realm configuration for subsequent calls to open a new Realm
    static func setDefaultConfig(){
        Realm.Configuration.defaultConfiguration = Realm.defaultConfig
    }
    
    /// Open the realm the first time on a background thread so that any migrations will not block the main thread
    /// There are two kinds of migrations that could happen:
    /// 1.  For local only Realms any schema change will trigger a migration and you should handle any custom migrations that may be required.  Realm will handle simple schema changes.
    /// 2. For Synced Realms and For Local Realms a new version of the SDK may migrate the database to a new storage format in which case the thread opening the Realm for the first time will block
    ///    while this is being performed.
    ///
    static func asyncInitialise(completion: @escaping (Bool, String)->Void) {
        
        DispatchQueue.global().async {
            // Set the default configuration so that if there are schema changes then the correct migrations
            // will be performed
            // You may also wish to perform compaction here.
            Realm.Configuration.defaultConfiguration = Realm.defaultConfig
            
            let configuration = Realm.defaultConfig
            do {
                let _ = try Realm(configuration: configuration)
                completion(true, "Database initialisation completed.")
            } catch {
                completion(false, "Error opening realm: \(error.localizedDescription)")
            }
        }
    }
}

Thanks for the code!

There is quite a bit of discussion (and confusion) about how to initially interact with Realm, and there are significant differences in that operation between a local only and a sync.

There are no current plans to alter the database format so that’s not something to be concerned about. The only significant change was when Realm became MongoDB Realm and the database needed to be changed to support Atlas NoSQL storage.

As far as blocking the background thread Realm is running on, it’s not really a problem - it’s by design, and if the pattern presented in the guide Sync Changes Between Devices - iOS SDK is followed, it’s not an issue. The following code prepares and executes the connection and sync’s the data. It’s all done on a background thread so the UI is not tied up and in the .success block, you know it’s ready to go.

let app = App(id: YOUR_REALM_APP_ID)
// Log in...
let user = app.currentUser
let partitionValue = "some partition value"
var configuration = user!.configuration(partitionValue: partitionValue)
Realm.asyncOpen(configuration: configuration) { result in
    switch result {
    case .failure(let error):
        print("Failed to open realm: \(error.localizedDescription)")
        // handle error
    case .success(let realm):
        print("Successfully opened realm: \(realm)")
        // Use realm
    }
}

If you are using a local only realm, none of that is needed as there is no synchronization - the data is always present.

I believe this is (finally) in the works (right, Realmers?). FYI and you may know this - Sync’d realms have no migrations. Additive changes are simple and fully supported, just update your object in code and you’re set. Destructive changes require a deletion of local data and re-sync with the server (per the above code). Local only Realms fully support migrations which is laid out in the guide Schema Versions & Migrations

Oh and this…

Realm.asyncInitialise(completion: {result, message in
   // Show for a minimum of 1 second or things look messy
   let elapsedTime = Date().timeIntervalSince(startTime)
   let delay = max(0, 1.0 - elapsedTime)

   DispatchQueue.main.asyncAfter(deadline: .now() + delay) {

Generally speaking, attempts to ‘work around’ an asynchronous call usually ends up in weird and intermittent operation. I would advise against that; work with async functions - let them drive the boat and then take action when they are done with their task within their closure. Trying to ‘guess’ at when an async function will complete is like trying to catch a bullet in the dark with a pair of pliers.

A little off topic but I thought I would include this:

During development, where you’re constantly changing object properties, stick with local only realm. It will save you a ton of time and avoids having to stop and restart the Realm server etc. Once you get the objects to a happy place, then implement sync’ing.

I think you may be referring to a different database format change - I am referring to the realm-core change - and there have been quite a few with SDK upgrades - where the local realm file gets upgraded to the new file format. This blocks the main thread while it is being performed if you open the realm on the main thread since the call to Realm() does not return until the upgrade has completed. Similarly if there is a schema change and a database migration needs to be performed this call to Realm() will also block while the migration is being performed if it is called on the main thread.

If you choose to use asyncOpen{} for sync realms then this will never call the callback if there is no network connection - so won’t work if your users may be offline. Should only be used for the first initialisation or for specific use cases where the network is available.

And WRT the ‘work around’ an async call - that code does not work around an async call - it is just making sure the UI does not flash a screen for a fraction of a second if the initial call to Realm() returns immediately by ensuring the screen is being displayed for at least 1 second before disappearing.

And lastly wrt to development with a local realm - that’s fine but the docs or some suggestions in posts here indicate that with a synced realm you should perform updates to objects on a background thread because of potential to block the main thread while the update is performed. Whereas there have been a few comments that one should avoid opening a realm on the main thread and on a background thread(s) if it is a local realm.

If you are planning to use sync in the future then your client app needs to take into consideration the effect of performing updates to objects on the main thread in a synced environment. In general we assume there app might be synced in future and code accordingly.

So far we have not had any issues with opening a local realm on the main thread and on background threads. it would be good to have a definitive document in your RealmSwift guide dealing with these issues rather than us having to rely on posts on the forum.

Anyway don’t take this as negative comment - RealmSwift is a game changer - just wish Apple would buy it or licence it and replace Core Data !!