RealmSwift downgrade migration - ios

Is it possible to detect the user is installing an app version with an older db schema version? (downgrading the app version basically)
Want to know if it is possible to detect and delete the current db file so that the app can still recover from it instead of just crashing.
It is an odd scenario but it is happening (some beta testers and such).

Yes, you have access to the old schema version in the migrationBlock of Realm.Configuration, so just check whether the oldSchemaVersion stored on the device is actually higher than the one your current app version has and if so, delete all Realm files using Realm.deleteFiles(for:).
let currentSchemaVersion = 1
let config = Realm.Configuration(
schemaVersion: currentSchemaVersion,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion > currentSchemaVersion) {
Realm.deleteFiles(for: Realm.Configuration.defaultConfiguration)
}
}
)
// Then set the config and create your `Realm` instance
Realm.Configuration.defaultConfiguration = config
let realm = try! Realm()

Related

Fatal Error :Can't open realm

I am working on IOS. I am using realm database in the frontend. It was working fine until I made some changes to the realm model and all files related to it. I just added one field to these files.
Now I am getting an error "Fatal error: Can't open realm" in the following code
fileprivate func getRealm() -> Realm {
// get default configuration realm
do {
return try Realm()
} catch {
Swift.fatalError("Can't open realm") //Fatal Error :Can't open realm
}
}
Can anyone tell what might be the possible causes for this error.
Thanks in advance.
If you made changes to the realm model, you need to increase your schema version and you may or may need to provide a migration block. See the official documentation for details.
// Inside your [AppDelegate didFinishLaunchingWithOptions:]
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
config.schemaVersion = 1;
// Set the block which will be called automatically when opening a Realm with a
// schema version lower than the one set above
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
};
// Tell Realm to use this new configuration object for the default Realm
[RLMRealmConfiguration setDefaultConfiguration:config];
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
[RLMRealm defaultRealm];
The above issue is caused due to inconsistency in the Realm database. I changed the structure of the model by adding one field. So, the model was having 10 fields but the realm database in my app was having 9 fields, as the app was built before the changes.
I solved the issue by just reinstalling the app which regenerated the realm database in the phone, thereby having a consistent model and database.
The above workaround might be good if your app is yet to be live. But, if your app is already being used by other users, then they will have to reinstall the app on update, which might result in bad user experience for the user.
Ideally, you should be writing a migration block for the new changes.
Hope, this is clear to all.

Realm migration inadequately documented. Can anyone clarify?

Performing migrations on a Realm database is poorly documented and the documentation seems to be out-of-date. There are two areas explaining how to migrate data:
-- The simple example on the Realm website: https://realm.io/docs/swift/latest/
-- A more detailed example in the Github examples section: https://github.com/realm/realm-cocoa/blob/master/examples/ios/swift-3.0/Migration/AppDelegate.swift
Neither of these examples adequately explain how to migrate data between schema versions. I've tried playing around with the examples and have yet to get any migrations working. As well, I've had problems with app crashes when upgrading to newer Realm versions without schema changes and data changes, which don't occur in the Simulator but occur when installing the app from TestFlight or the App Store.
Seems like the Realm documentation and examples detailing migrations are due for a refresh. My areas of interest are:
Upgrading to a newer Realm version without a schema change in the database. Unclear whether I should continue using the default.realm file generated with a previous version, or whether I need to re-generate the default.realm file using the newer Realm framework version.
Adding a new attribute to a Realm object.
New objects ("rows") added to an existing class without any schema change.
No schema changes to existing classes in the database, but addition of an entirely new class or classes.
Any combination of the above.
Thanks!
Sorry the docs are not sufficient. We appreciate the feedback and will use it to improve them. In the mean time, let me answer your questions:
You do not need to do anything when you upgrade the SDK. Sometimes, we upgrade the core database file format, but this migration happens automatically when you open the Realm (Realm()) so you don't have to worry about it.
When you add a new property to an object you can just follow this code snippet.
Nothing is needed in the migration block since this block is simply to apply data transformations between versions. All you need to do is increment the schemaVersion
// Inside your application(application:didFinishLaunchingWithOptions:)
let config = Realm.Configuration(
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
schemaVersion: 1,
// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
})
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
let realm = try! Realm()
Adding objects to a Realm does not affect the schema so a migration is not relevant.
This is the same as 2, you simply need to increment the schemaVersion but you don't have to do anything in the migration block since Realm handles everything. The migration block is for custom migration logic where, for example, you want to transform firstName and lastName from schemaVersion=0 to fullName when updating to schemaVersion=1. In this case, you could get the data from the old version and concatenate the strings into the new fullName property within the migration block.
Hope this helps!
Unfortunately, that doesn't work, at all. There is a block of code I had to add to iOS apps several months ago in order to get Realm to work, but it doesn't solve the migration problems I've experienced:
config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("default-v1.realm")
Realm.Configuration.defaultConfiguration = config
// copy over old data files for migration
//let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
//print("Realm.Configuration.defaultConfiguration.fileURL! = \(defaultURL)")
//let defaultParentURL = defaultURL.URLByDeletingLastPathComponent
if let realmURL = bundleURL("default-v1") {
do {
//print("Bundle location for default.realm = \(realmURL)")
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = URL(fileURLWithPath: path)
let filePath = url.appendingPathComponent("default-v1.realm").path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("FILE INSTALLED")
print("Documents location for default.realm = \(filePath)")
} else {
//print("FILE NOT INSTALLED, COPYING default.realm from bundle to Documents directory.")
try FileManager.default.copyItem(at: realmURL, to: defaultURL)
}
} catch {}
}
I suspect that a new default-v1.realm file may clobber an existing default-v1.realm file on a user's device, using this code with the "Nothing is needed" migration method.
Part of the confusion stems from the example migration code at https://github.com/realm/realm-cocoa/tree/master/examples/ios/swift-3.0/Migration where data from the v1 file is getting copied over to the v2 file. Regardless, I've tried the "Nothing is needed" code you've posted and the app doesn't find any Realm data then crashes.
It looks like I could splice the code you've posted into the code I've been using then perhaps that would solve the problem. Will look into that next week.
Thanks for your response!

Realm migration when using non-default configurations

In the realm docs I see the code example for migrations
let config = Realm.Configuration(
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
schemaVersion: 1,
// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
})
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
and then it says to use let realm = try! Realm() to get the realm instance. However, in our application we are using our own realm configurations with something like
let realm = try! Realm(configuration: RealmConfig.getConfig(typeURL: .userData)!)
We have a few different configurations besides .userData. My question is, how does one go about doing migrations with these non-default configurations? The code example really only shows how to set the default configuration which is insignificant for my use. I couldn't find anything like
Realm.Configuration.userData = config
Does something like this exist that I am missing? Or is there another way I'm supposed to go about this?
You can instantiate different Realm instances with different configurations like this:
let userDataConfiguration = Realm.Configuration(...)
let userDataRealm = try! Realm(configuration: userDataConfiguration)
Learn more in docs at https://realm.io/docs/swift/latest/#realms

AppStore update and Realm

I have application in Appstore based on Realm Mobile Database. I'd like to prepare version 1.1. Will update delete all data in user's local database ?
The upgrade won't delete anything on your current database. The migration is automatic. If you need to change one or more field in your current model, you need to upgrade the database schema version. Place this code into the application:didFinishLaunchingWithOptions: method in your AppDelegate:
//Realm migration
let config = Realm.Configuration(
schemaVersion: 2, //here's the schema version you need to change
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 2) {
//if you want to perform particular tasks
//while migrating, place your code here.
}
})
Realm.Configuration.defaultConfiguration = config
_ = try! Realm()
Absolutely not, It won't delete existing database, that's why Realm support automatic/custom migration.

Run Migration on synced Realm

I've created a new object model and when I open my app I get the following error:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Invalid class subset list:
- 'Mod.generalSettings' links to class 'Setting', which is missing from the list of classes managed by the Realm
- 'Mod.contextSettings' links to class 'Setting', which is missing from the list of classes managed by the Realm
- 'Mod.accountSettings' links to class 'Setting', which is missing from the list of classes managed by the Realm'
I don't get this error when I shut down the Realm Object Server that holds my synced Realm. This is the config that runs on launch:
Realm.Configuration.defaultConfiguration = Realm.Configuration(
syncConfiguration: (user, syncServerURL!),
objectTypes: [Dot.self, Mod.self, Setting.self])
This leads me to believe I need to run a migration for my remote realm. How can I do this?
You don't have direct access to Realm files on the server from the client. All you need to do is run a migration locally and the changes will be pushed up to the server.
If you're not deleting any columns, you can run a migration as simply as:
Realm.Configuration.defaultConfiguration = Realm.Configuration(
syncConfiguration: (user, syncServerURL!),
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in },
objectTypes: [Dot.self, Mod.self, Setting.self])
I hope that helps!

Resources