Migration with Realm (RealmSwift Part 6)
In this part we will cover following topics
- Why schema migration
- Schema Versioning in Realm
- Automatic Realm Migration
- Custom Realm Migration with examples
This is the continuation of the previous part . If you know the basics operation in Realm you can continue this .Download the starter project with title “MigrationRealm” and as shown in Figure 1 we created User model and saved it into the realm fair enough. Add this code in viewDidLoad
and run the application
As shown in Figure 2 we did a couple of things
- Added
do
catch
block for proper error handling - Added
title
as new property inUser
model
Now run the application again and you will get the error “Migration is required” since Realm file failed to open and you can’t perform realm operation because every time you try to open the realm file with the data saved with the previous schema and you updated schema in your next run/update case your Realm()
will always throw an error. If you force try
realm your application will crash
let realm = try! Realm() . //In this case application will crash
Why Schema Migration
Imagine you have a User app installed on the user’s iPhone. The app creates a Realm file in the Documents folder and uses it to persist objects. At this point if you had saved any data with the previous model version, there will be a mismatch between what Realm sees defined in code and the data Realm sees on disk. When this occurs, an exception will be thrown when you try to open the existing file unless you run a migration. For our previous case new code expect title
field but previously saved objects don’t have this field so there is a mismatch
Solution 1 (Delete old file)
Delete the application and run again with the code shown in Figure 3 and you will see we configured realm with deleteRealmIfMigrationNeeded = true
which means when there is a mismatch delete the Realm file and create again. In this case you will delete the user’s data every time there is a migration required which is not a good solution
As shown in Figure 4 we updated schema by adding title property in User model and realm saw mismatch it deleted old data and created fresh file and you can see we lost User whose id = 20
Note: You use this property mostly in development phase. Don’t shipped with production code unless you will get low rating
Solution 2 (Schema Versioning Automatic Migration)
Delete the application and run again with the code shown in Figure 5 and you will see we configured realm with schemaVersion = 1
which means we are telling realm it’s our first version of the schama and if we increment this value do automatic migration.
As shown in Figure 6 we updated model schema since we need to do migration we changed schemaVersion = 2,
realm introspect difference in schemaVersion it will do migration itself and you can see we opened realm successfully without deleting our old data. One thing to note User
with Id = 21
was previously stored it get empty title field
As documented “Note that default property values aren’t applied to new objects or new properties on existing objects during migrations. We consider this to be a bug, and are tracking it as #1793.”
Solution 2 (Schema Versioning Custom Migration)
What if you want your old schema who don’t have title
property when upgraded to new version will have default value. Let’s do it, before start first delete the application and write this code as shown in Figure 7. We saved user object into the realm.
Now application evolved and they decided to add title
property in User
model because server is sending this field which we need to show in our latest UI. As you can see we performed number of tasks
- Incremented
schemaVersion
to tell realm perform automatic migration (schema migration) - Specified migration block contains code that Realm runs once during the schema migration, and it provides access to both the old and new schemas. When realm see difference in schema version it will run this block
migrationBlock
provides two closure arguments:first
→ migration is an instance of the Migration helper class, which gives you access to the old and new object schemas and provides you with methods to access the data in the old and new Realm files.second →
oldVersion is the schema version of the existing file, e.g., the one you’re migrating from and in our case it’s value is 1 since we are migrating from 1 to 2- Iterate over all objects in the database stored with previous version and one thing to note both
old
andnew
parameter represents the same data stored butold
represent the data with older scheme andnew
with newest schema and as shown in Figure 8 since we addedtitle
field on new schema and we are accessing this with the new model and assigning value to it
Note: Migration only done to the data stored with the previous schema and if put it in our previous case
old
represents theUserId = 20
with previous schema which don’t havetitle
field and new represents theUserId = 20
with newer schema which havetitle
field but point to note both represents the same userYou can access the existing data by enumerating over all of the objects of a certain type. The API is similar to Realm.objects(_), with the main difference being that it provides you with the objects from both the old and new Realms simultaneously.
More Custom Migration Examples
Delete the application first and as shown in Figure 9 we created User with our first schema version and saved into the realm, so far so good
As shown in Figure 10 we performed two custom migrations
- In previous stored data we saved User
title
withnil
, now in our next release if we want to give some value to it we can do it , Note both new and old have title field since both version of the schema has title property - Secondly
Migration.renameProperty(onType:from:to:)
. When you rename a property in your code, Realm has no way to know that a property represents the same thing, given the fact it has a different name.
For example, when you renamecurrentUser
toisCurrentUser
, the only information Realm has is thatcurrentUser
was deleted andisCurrentUser
was added. Therefore, it deletes all data stored incurrentUser
and adds a new empty property namedisCurrentUser
to the class. To preserve the existing data stored on disk, you need to help Realm by adding the following to your migration block:
Now application evolved and User now have a 1:1 relationship with the Passport model and as shown in Figure 11 we added Passport model as well and created a relationship with the User model
As shown in Figure 12 let’s imagine User is migrating from schema version 2 to 3 for the simple case and we don’t cater 1 to 3 migration. You can see we performed number of tasks
- Incremented the schemaVersion from 2 to 3
- If User are migrating from 2 to 3 we created Passport model and assign it to the existing users data stored with previous schema in which there is no passport property
- As shown in console “migration passport Number” is printed on the existing data as well which means migration went well
Migration.create(_:value:) creates a new MigrationObject. You provide a class name matching a local Realm object (as a String) and a dictionary to initialize the object with.
Rule of Thumb :
Avoid Custom migration as much as possible because when you do you need to think many flows
Useful Links
https://store.raywenderlich.com/products/realm-building-modern-swift-apps-with-realm-database