Migrating Your iOS App's Synced Realm Schema in Production
Andrew MorganPublished Sep 02, 2021 • Updated Mar 06, 2023
Rate this tutorial
In the previous post in this series, we saw how to migrate your Realm data when you upgraded your iOS app with a new schema. But, that only handled the data in your local, standalone Realm database. What if you're using MongoDB Realm Sync to replicate your local Realm data with other instances of your mobile app and with MongoDB Atlas? That's what this article will focus on.
We'll start with the original RChat app. We'll then extend the iOS app and backend Realm schema to add a new feature that allows chat messages to be tagged as high priority. The next (and perhaps surprisingly more complicated from a Realm perspective) upgrade is to make the
authorattribute of the existing
You can find all of the code for this post in the RChat repo under these branches:
Realm Cocoa 10.13.0 or later (for versions of the app that you're upgrading to)
RChat is a basic chat app:
- Users can register and log in using their email address and a password.
- Users can create chat rooms and include other users in those rooms.
- Users can post messages to a chat room (optionally including their location and photos).
- All members of a chatroom can see messages sent to the room by themselves or other users.
The first update is to allow a user to tag a message as being high-priority as they post it to the chat room:
That message is then highlighted with bold text and a "hot" icon in the list of chat messages:
Adding a new field is an additive change—meaning that you don't need to restart sync (which would require every deployed instance of the RChat mobile app to recognize the change and start sync from scratch, potentially losing local changes).
We add the new
isHighPrioritybool to our Realm schema through the Realm UI:
We also make
isHighPrioritya required (non-optional field).
The resulting schema looks like this:
Note that existing versions of our iOS RChat app can continue to work with our updated backend Realm app, even though their local
ChatMessageRealm objects don't include the new field.
While existing versions of the iOS RChat app can continue to work with the updated Realm backend app, they can't use the new
isHighPriorityfield as it isn't part of the
To add the new feature, we need to update the mobile app after deploying the updated Realm backend application.
The first change is to add the
isHighPriorityfield to the
As seen in the previous post in this series, Realm can automatically update the local realm to include this new attribute and initialize it to
false. Unlike with standalone realms, we don't need to signal to the Realm SDK that we've updated the schema by providing a schema version.
The new version of the app will happily exchange messages with instances of the original app on other devices (via our updated backend Realm app).
When the initial version of RChat was written, the
ChatMessagewas declared as being optional. We've since realized that there are no scenarios where we wouldn't want the author included in a chat message. To make sure that no existing or future client apps neglect to include the author, we need to update our schema to make
authora required field.
Unfortunately, changing a field from optional to required (or vice versa) is a destructive change, and so would break sync for any deployed instances of the RChat app.
This means that there's extra work needed to make the upgrade seamless for the end users. We'll go through the process now.
The change we need to make to the schema is destructive. This means that the new document schema is incompatible with the schema that's currently being used in our mobile app.
If RChat wasn't already deployed on the devices of hundreds of millions of users (we can dream!), then we could update the Realm schema for the
ChatMessagecollection and restart Realm Sync. During development, we can simply remove the original RChat mobile app and then install an updated version on our test devices.
To avoid that trauma for our end users, we leave the
ChatMessagecollection's schema as is and create a partner collection. The partner collection (
ChatMessageV2) will contain the same data as
ChatMessage, except that its schema makes
authora required field.
These are the steps we'll go through to create the partner collection:
- Define a Realm schema for the
- Run an aggregation to copy all of the documents from
authoris missing from a
ChatMessagedocument, then the aggregation will add it.
- Add a trigger to the
ChatMessagecollection to propagate any changes to
- Add a trigger to the
ChatMessageV2collection to propagate any changes to
From the Realm UI, copy the schema from the
Click the button to create a new schema:
Set the database and collection name before clicking "Add Collection":
Paste in the schema copied from
requiredsection, change the
ChatMessageV2, and the click the "SAVE" button:
This is the resulting schema:
We're going to use an aggregation pipeline to copy and transform the existing data from the original collection (
ChatMessage) to the partner collection (
You may want to pause sync just before you run the aggregation, and then unpause it after you enable the trigger on the
ChatMessagecollection in the next step:
The end users can continue to create new messages while sync is paused, but those messages won't be published to other users until sync is resumed. By pausing sync, you can ensure that all new messages will make it into the partner collection (and so be visible to users running the new version of the mobile app).
If pausing sync is too much of an inconvenience, then you could create a temporary trigger on the
ChatMessagecollection that will copy and transform document inserts to the
ChatMessageV2collection (it's a subset of the
ChatMessageProptrigger we'll define in the next section.).
From the Atlas UI, select "Collections" -> "ChatMessage", "New Pipeline From Text":
Paste in this aggregation pipeline and click the "Create New" button:
This aggregation will take each
authorto "unknown" if it's not already set, and then add it to the
Click "MERGE DOCUMENTS":
ChatMessageV2now contains a (possibly transformed) copy of every document from
ChatMessage. But, changes to one collection won't be propagated to the other. To address that, we add a database trigger to each collection…
We need to create two Realm Functions—one to copy/transfer documents to
ChatMessageV2, and one to copy documents to
From the "Functions" section of the Realm UI, click "Create New Function":
Name the function
copyToChatMessageV2. Set the authentication method to "System"—this will circumvent any access permissions on the
ChatMessageV2collection. Ensure that the "Private" switch is turned on—that means that the function can be called from a trigger, but not directly from a frontend app. Click "Save":
Paste this code into the function editor and save:
This function will receive a
ChatMessagedocument from our trigger. If the operation that triggered the function is a delete, then this function deletes the matching document from
ChatMessageV2. Otherwise, the function either copies
authorfrom the incoming document or sets it to "Unknown" before writing the transformed document to
ChatMessageV2. We could initialize
authorto any string, but I've used "Unknown" to tell the user that we don't know who the author was.
copyToChatMessagefunction in the same way:
The final change needed to the backend Realm application is to add database triggers that invoke these functions.
From the "Triggers" section of the Realm UI, click "Add a Trigger":
ChatMessageProptrigger as shown:
If you paused sync in the previous section, then you can now unpause it.
We want to ensure that users still running the old version of the app can continue to exchange messages with users running the latest version.
Existing versions of RChat will continue to work. They will create
ChatMessageobjects which will get synced to the
ChatMessageAtlas collection. The database triggers will then copy/transform the document to the
We now need to create a new version of the app that works with documents from the
ChatMessageV2collection. We'll cover that in this section.
Recall that we set
ChatMessageV2in the partner collection's schema. That means that to sync with that collection, we need to rename the
ChatMessageV2in the iOS app.
Changing the name of the class throughout the app is made trivial by Xcode.
ChatMessage.swiftand right-click on the class name (
ChatMessage), select "Refactor" and then "Rename…":
Override the class name with
ChatMessageV2and click "Rename":
The final step is to make the author field mandatory. Remove the ? from the author attribute to make it non-optional:
Modifying a Realm schema is a little more complicated when you're using Realm Sync for a deployed app. You'll have end users who are using older versions of the schema, and those apps need to continue to work.
Fortunately, the most common schema changes (adding or removing fields) are additive. They simply require updates to the back end and iOS schema, together.
Things get a little trickier for destructive changes, such as changing the type or optionality of an existing field. For these cases, you need to create and maintain a partner collection to avoid loss of data or service for your users.
This article has stepped through how to handle both additive and destructive schema changes, allowing you to add new features or fix issues in your apps without impacting users running older versions of your app.
Remember, you can find all of the code for this post in the RChat repo under these branches:
If you're looking to upgrade the Realm schema for an iOS app that isn't using Realm Sync, then refer to the previous post in this series.
If you have any questions or comments on this post (or anything else Realm-related), then please raise them on our community forum. To keep up with the latest Realm news, follow @realm on Twitter and join the Realm global community.
Build an Offline-First React Native Mobile App with Expo and Realm React Native
Aug 26, 2022