Hello, I found a use case that I’m not sure how to handle:
Add a required propertyprovider to the CalendarDetails collection schema (defined as String, but in the swift client it’s enum CalendarAccountType: String, PersistableEnum).
After updating the schema, add the property to all objects using an aggregation (with a valid enum value).
Launch the updated client app that now has the enum property, opening the Realm with downloadBeforeOpen set to .once, as usual.
Notice that the realm does not download the changes in the aggregation, which results in trying to create the enum with an empty string and crashing in ComplexTypes.swift: extension RawRepresentable: public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> Self (in the return statement there’s a force unwrap).
If I reinstall the app or set downloadBeforeOpen to .always, the changes are downloaded and the enum value is set properly.
Is this intended? I thought changes will download automatically if there is a schema change. If so, what is the best way to approach this?
I implemented a basic schema versioning like the local Realm works, and if the client is updated and the new version is higher, then I set downloadBeforeOpen to .always and it works fine.
Also, is it better to first update the collections and then the schema?
Hi, thank you for the detailed post. You have run into what could be a few different things and we are working on making each of them better in different ways.
The first thing you might be running into is that when changes are made to the schema, the component in charge of replicating data between MongoDB and Device Sync performs an initial sync on the new field(s) to ingest the values of that field for existing documents and send it down to the device. This is an asynchronous process and takes time relative to the number of documents in the collection. It does seem possible thaty= you observed a timing issue here and the changes just took some time to replicate. We are working on a larger initiative to address this in the coming months.
The second thing that comes to mind is that when you create a required field, all documents in MongoDB without that field become “invalid” and do not get synced. See here for more details: https://www.mongodb.com/docs/atlas/app-services/sync/data-model/update-schema/#add-a-required-property. We are working on adding more documentation around it, but you can actually add the “default” keyword to the JSON schema in the cloud-ui to define what value should be sent if MongoDB does not find a value.
Let me know if it sounds like one of these is at play here. I would suggest at the very least populating the documents to have the new required field before adding the new field to the schema to prevent them from becoming unsyncable and then re-syncable (swap steps 1 and 2). Then if you give it some time (you should see a banner in the UI) the values for those fields should make it to the devices without needing to reset the device at all.
Hi Tyler, thanks for the details.
I didn’t know about the default value, I just used it for another schema change (added a required property) and I had no issues.
This is actually a good way to have the app work with old mobile clients who can create unsyncable data, without having to set up triggers and do migrations for simple changes. It’s weird that I don’t remember seeing it in the docs.
I forgot to mention one thing - I also tried doing a realm refresh and syncSession resume/reconnect right after opening the realm (with downloadBeforeOpen set to .once) but it still didn’t sync, even though I had added the required property in the database.
Could it be that the objects were not marked back as syncable after I added the data to comply with the schema?
Hi, when set to .once it means that the changes have not all been downloaded on subsequent opens of the realm (only on the first ever one). I think to get the behaviour you are expecting you either need to have .always or you can manually use this method: SyncSession | Realm JavaScript - v12.4.0
I looked at the SyncSession methods of the RealmSwift package and there is no download (or similar) method, only the ones I mentioned: resume and reconnect. Maybe in Swift there is no way to force download the changes?
I have a related use case for using default that seems to not be working.
I have a collection named AppUser where there is an embedded object property called preferences (type name: Preferences).
I just added a required property in Preferences called enableDailySummaryNotifications, generated the schema using the client and debug mode, and when I test with an older client where the new property is missing from client code, the property always gets set to false when I create a new AppUser object.
I tried adding "default": true or "default": "true" to the Preferences schema but it has no effect. All new AppUser objects created with an old client have preferences.dailyNotificationRequestIdentifier set to false.
Hi, the server-side definitions for default values for fields are only used when documents are created in MongoDB. So if you create a document with the enableDailySummaryNotifications field missing, you should see it replicate to devices with a default value of true if you set { default: true } .
We do not do the same thing for writes made from devices. We have a ticket to consider doing it, but the reason we have not done it yet is that it would create a data inconsistency. The concept of default values is not present in all SDKs and not communicated between the client and the server. When a realm object is created, all required fields are implicitly created and set to the default values for that type (false, “”, 0, etc). So if we started interpreting those as a new value, then the client that issued the write would not also see that value.
I see. I’m already using clients defaults but this is about outdated clients that don’t have the new property.
So the only way to add a custom default is to set up a trigger for object insertions and set the default. When all users will have updated the client app then the client default will work 100% of the time and I can remove the trigger.
Is this correct?