Realm data doesn't show up on a physical device - swift

I'm developing an app with realmSwift. I want to set preset data on realm db initially, then I want it to show up when user open the app and to make it writable. I write some code based on other questions about realm bundle. such as Realm - Add file with initial data to project (iOS/Swift) and Realm fileExists is always true
Then I managed to do what I want to on simulator but it doesn't work on a physical device. This is the code I wrote on AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
openRealm()
let config = Realm.Configuration(
fileURL: Bundle.main.url(forResource: "default", withExtension: "realm"),
readOnly: true)
var realm = try! Realm(configuration: config)
print(Realm.Configuration.defaultConfiguration.fileURL)
return true
}
func openRealm() {
let realm = try! Realm()
let bundlePath = Bundle.main.path(forResource: "default", ofType: "realm")
let destPath = Realm.Configuration.defaultConfiguration.fileURL?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: destPath!) {
//File exist, do nothing
print("File exist")
try! fileManager.removeItem(atPath: destPath!)
do {
//Copy file from bundle to Realm default path
try fileManager.copyItem(atPath: bundlePath!, toPath: destPath!)
print("Copied")
} catch {
print("\n",error)
}
} else {
do {
try fileManager.copyItem(atPath: bundlePath!, toPath: destPath!)
print("Copied")
} catch {
print("\n",error)
}
}
}

There are 3 issue with your code: firstly, you shouldn't be using the true part of your if branch, since as long as you run openRealm() from your AppDelegate's applicationDidFinishLaunching and before you'd make any calls to Realm the only reason why a default.realm file can exist is because your app isn't launching for the first time, so you shouldn't overwrite it.
Secondly, you shouldn't be calling let realm = try! Realm() at the beginning of your openRealm function since that will actually create a realm, which will make harder it than it should be to detect if the prepopulated .realm file was already copied to the defaultPath or not. You don't actually need to call try! Realm() at all in the openRealm() function unless you want to perform a migration before your Realm could be opened from anywhere else in your code.
func openRealm() {
let bundlePath = Bundle.main.path(forResource: "default", ofType: "realm")!
let defaultPath = Realm.Configuration.defaultConfiguration.fileURL?.path!
let fileManager = FileManager.default
// Only need to copy the prepopulated `.realm` file if it doesn't exist yet
if !fileManager.fileExists(atPath: defaultPath){
print("use pre-populated database")
do {
try fileManager.copyItem(atPath: bundlePath, toPath: defaultPath)
print("Copied")
} catch {
print(error)
}
}
Lastly, you shouldn't be creating a new configuration where you overwrite the path of Realm with your bundlePath since files in the application bundle shouldn't be modified ever (that will break your code signature). Moreover, you already copied your prepopulated file from your application bundle to Realm's defaultPath, so if you simply call Realm(), it will be modifying the prepopulated file, since it is stored at the default location.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
openRealm()
print(Realm.Configuration.defaultConfiguration.fileURL)
return true
}

Related

How to pre-load Core Data with a SQLite file that have references to images that were saved using "external storage"?

My goal is pre-loading Core Data, at the first app launch. So far I ran a simulation and filled Core Data with data. (I had checked "allow external Storage").
I went into application_support and copied: MyApp.sqlite-wal, MyApp.sqlite-shm, .MyApp_SUPPORT/_EXTERNAL_DATA/ and MyApp.sqlite.
Then I added the MyApp.sqlite file in my app bundle and added this code in my app delegate:
lazy var persistentContainer: NSPersistentContainer = {
let modelName = "MyApp"
var container: NSPersistentContainer!
container = NSPersistentContainer(name: modelName)
// Preloading
let appName: String = "MyApp"
var persistentStoreDescriptions: NSPersistentStoreDescription
let storeUrl = self.getDocumentsDirectory().appendingPathComponent("MyApp.sqlite")
if !FileManager.default.fileExists(atPath: (storeUrl.path)) {
let seededDataUrl = Bundle.main.url(forResource: appName, withExtension: "sqlite")
try! FileManager.default.copyItem(at: seededDataUrl!, to: storeUrl)
}
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
description.url = storeUrl
container.persistentStoreDescriptions = [description]
//End Preloading
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
It works but It looks like it doesn't find the images that were saved in external storage. They're present in .MyApp_SUPPORT/_EXTERNAL_DATA as references.
Where should I add the references?
Load everything is my goal.
If you have a file named MyApp.sqlite with external binary storage in some directory (here, the app bundle), Core Data puts those files in a sub-directory named .MyApp_SUPPORT/_EXTERNAL_DATA/. You need to recursively copy that directory and everything in its sub-directories.
Using that path is not a good idea, though, because it's undocumented and could change without warning. Also, this will miss MyApp.sqlite-wal and MyApp.sqlite-shm, if they exist.
A better idea is to put the seed store in a custom directory of its own, and copy everything from that directory. Instead of just having MyApp.sqlite, you'd have a directory named MyAppSeedData which would contain MyApp.sqlite. It would also contain all the other stuff Core Data needs like the external binary files. Use the same FileManager function to copy the MyAppSeedData directory (because it will recursively copy every file) and you should be fine.
The code to copy the folder would be something like this:
if let sourceDirURL = Bundle.main.url(forResource: "Source Folder", withExtension: nil) {
let destinationDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("TestFolder")
if !FileManager.default.fileExists(atPath: destinationDirURL.path) {
do {
try FileManager.default.copyItem(at: sourceDirURL, to: destinationDirURL)
} catch {
print("Error copying directory: \(error)")
}
}
}
You could then add the SQLite file name to the end of destinationDirURL and use it for Core Data.
Step 1: Create "MyAppSeedData" dir and paste MyApp.sqlite, the MyApp_SUPPORT, the MyApp.sqilte-smh, MyApp.sqilte-wal files inside.
Step 2: Drag MyAppSeedData to the bundle under AppDelegate and tick the box add target.
Step 3: These functions must be in AppDelegate file:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
//If first launch condition == true {
seedData()
//}
return true
}
func seedData() {
let fm = FileManager.default
//Destination URL: Application Folder
let libURL = fm.urls(for: .libraryDirectory, in: .userDomainMask).first!
let destFolder = libURL.appendingPathComponent("Application Support").path
//Or
//let l1 = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).last!
//
//Starting URL: MyAppSeedData dir
let folderPath = Bundle.main.resourceURL!.appendingPathComponent("MyAppSeedData").path
let fileManager = FileManager.default
let urls = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)
if let applicationSupportURL = urls.last {
do{
try fileManager.createDirectory(at: applicationSupportURL, withIntermediateDirectories: true, attributes: nil)
}
catch{
print(error)
}
}
copyFiles(pathFromBundle: folderPath, pathDestDocs: destFolder)
}
func copyFiles(pathFromBundle : String, pathDestDocs: String) {
let fm = FileManager.default
do {
let filelist = try fm.contentsOfDirectory(atPath: pathFromBundle)
let fileDestList = try fm.contentsOfDirectory(atPath: pathDestDocs)
for filename in fileDestList {
try FileManager.default.removeItem(atPath: "\(pathDestDocs)/\(filename)")
}
for filename in filelist {
try? fm.copyItem(atPath: "\(pathFromBundle)/\(filename)", toPath: "\(pathDestDocs)/\(filename)")
}
} catch {
print("Error info: \(error)")
}
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
let modelName = "MyApp"
var container: NSPersistentContainer!
container = NSPersistentContainer(name: modelName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()

Trouble fetching database path

Integrated SQLITE Database, below is my code which I have written,
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
self.copyDatabaseIfNeeded()
return true
}
func copyDatabaseIfNeeded() {
let fileManager = FileManager.default
let dbPath = getDBPath()
var success: Bool = fileManager.fileExists(atPath: dbPath!)
if !success {
let defaultDBPath = URL(fileURLWithPath: Bundle.main.resourcePath ?? "").appendingPathComponent(APPLICATION_DB).absoluteString
success = ((try?fileManager.copyItem(atPath: defaultDBPath, toPath: dbPath!)) != nil)
if !success {
print("Failed to create writable database!")
}
}
}
func getDBPath() -> String? {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDir = paths[0]
return URL(fileURLWithPath: documentsDir).appendingPathComponent(APPLICATION_DB).absoluteString
}
It always print below output,
Tried clean + Build Project
Uninstall the application, reinstall it
Remove derived data
none of the above has worked.
Also, my sqlite file is there in project target -> Build Phases -> Copy Bundle Resources : below screenshot for reference.
I am not sure whether hierarchy of my file matters or not, attaching screenshot of that as well.
Can anyone help me why I am getting issue in fetching file path of my database file?
Try this.
func copyDatabaseIfNeeded() {
// Move database file from bundle to documents folder
let fileManager = FileManager.default
let documentsUrl = fileManager.urls(for: .documentDirectory,
in: .userDomainMask)
guard documentsUrl.count != 0 else {
return // Could not find documents URL
}
let finalDatabaseURL = documentsUrl.first!.appendingPathComponent("SQL.sqlite")
if !( (try? finalDatabaseURL.checkResourceIsReachable()) ?? false) {
print("DB does not exist in documents folder")
let documentsURL = Bundle.main.resourceURL?.appendingPathComponent("SQL.sqlite")
do {
try fileManager.copyItem(atPath: (documentsURL?.path)!, toPath: finalDatabaseURL.path)
} catch let error as NSError {
print("Couldn't copy file to final location! Error:\(error.description)")
}
} else {
print("Database file found at path: \(finalDatabaseURL.path)")
}
}

Converting local realms to synced realm

I want to convert my local realm database to a synced one to allow user authentication. I'm using swift and it wasn't included in the documentation, however, I found this method but it keeps giving SIGABRT exception and I don't know what seems to be the problem.
Here's what I added in the App Delegate:
import UIKit
import RealmSwift
import Realm
import Realm.Dynamic
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let sourceFilePath = Bundle.main.url(forResource: "fieldFlow", withExtension: "realm")
let configuration = RLMRealmConfiguration()
configuration.fileURL = sourceFilePath
configuration.dynamic = true
configuration.readOnly = true
let localRealm = try! RLMRealm(configuration: configuration)
let creds = SyncCredentials.usernamePassword(username: "admin#realm.io", password: "password")
SyncUser.logIn(with: creds, server: URL(string: "http://localhost:9080")!) { (syncUser, error) in
DispatchQueue.main.async {
if let syncUser = syncUser {
self.copyToSyncRealmWithRealm(realm: localRealm, user: syncUser)
}
}
}
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()
// Override point for customization after application launch.
return true
}
func copyToSyncRealmWithRealm(realm: RLMRealm, user: RLMSyncUser) {
let syncConfig = RLMRealmConfiguration()
syncConfig.syncConfiguration = RLMSyncConfiguration(user: user, realmURL: URL(string: "realm://localhost:9080/~/fieldRow")!)
syncConfig.customSchema = realm.schema
let syncRealm = try! RLMRealm(configuration: syncConfig)
syncRealm.schema = syncConfig.customSchema!
try! syncRealm.transaction {
let objectSchema = syncConfig.customSchema!.objectSchema
for schema in objectSchema {
let allObjects = realm.allObjects(schema.className)
for i in 0..<allObjects.count {
let object = allObjects[i]
RLMCreateObjectInRealmWithValue(syncRealm, schema.className, object, true)
}
}
}
}

Existing Realm Database notworking in swift 3.1 xcode 8

I created the application use existing database realm 2.3.0 and swift 3.1 xcode 8.3.
But when I try to access the realm database. there is an error.
Could not access database: Error Domain=io.realm Code=2 "Unable to open a realm at path '/Users/dodipurnomo/Library/Developer/CoreSimulator/Devices/858C796B-CBA8-424B-9A97-0893304B758B/data/Containers/Data/Application/A2D910EE-AAC5-4836-9FE7-97F744E802E5/Documents/Conversio.realm': Unsupported Realm file format version." UserInfo={NSFilePath=/Users/dodipurnomo/Library/Developer/CoreSimulator/Devices/858C796B-CBA8-424B-9A97-0893304B758B/data/Containers/Data/Application/A2D910EE-AAC5-4836-9FE7-97F744E802E5/Documents/Conversio.realm,
Above is an error message when I try to execute the database.
As for the class to hendleing the database realm is as follows:
import RealmSwift
import UIKit
class DBManager{
//MARK: - Singleton shared intance
static let sharedIntance = DBManager()
//MARK: - overide init function in realm
static var realm: Realm {
get {
do {
let realm = try Realm()
return realm
}
catch {
print("Could not access database: ", error)
}
return self.realm
}
}
public static func write(realm: Realm, writeClosure: () -> ()) {
do {
try realm.write {
writeClosure()
}
} catch {
print("Could not write to database: ", error)
}
}
public static func query(realm: Realm,queryClosure: () -> ()){
}
func save(entityList: [Object], shouldUpdate update: Bool = false) {
DBManager.realm.beginWrite()
for entity in entityList {
if let key = type(of: entity).primaryKey(), let value = entity[key] , update {
if let existingObject = DBManager.realm.object(ofType: type(of: entity), forPrimaryKey: value as AnyObject) {
let relationships = existingObject.objectSchema.properties.filter {
$0.type == .array
}
for relationship in relationships {
if let newObjectRelationship = entity[relationship.name] as? ListBase , newObjectRelationship.count == 0 {
entity[relationship.name] = existingObject[relationship.name]
}
}
}
}
DBManager.realm.add(entity, update: update)
}
do {
try DBManager.realm.commitWrite()
} catch let writeError {
debugPrint("Unable to commit write: \(writeError)")
}
DBManager.realm.refresh()
}
}
And I set the Realm in appdelegate as follows:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let desPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let fullDesPath = URL(fileURLWithPath: desPath).appendingPathComponent("Conversio.realm")
var config = Realm.Configuration()
config.deleteRealmIfMigrationNeeded = true
config.fileURL = fullDesPath
Realm.Configuration.defaultConfiguration = config
chekDB()
return true
}
//chek database
func chekDB() {
let bundleDB = Bundle.main.path(forResource: "Conversio", ofType: "realm")
let desPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let fileManager = FileManager.default
let fullDesPath = URL(fileURLWithPath: desPath).appendingPathComponent("Conversio.realm")
let fullDestPathString = String(describing: fullDesPath)
if fileManager.fileExists(atPath: fullDesPath.path){
print("Database file is exis !")
print(fileManager.fileExists(atPath: bundleDB!))
}else{
do{
try fileManager.copyItem(atPath: bundleDB!, toPath: fullDesPath.path)
}catch{
print("error encured while copying file to directori \(fullDestPathString)")
}
}
}
The error message you're getting means that realm file was created with newer version of Realm, so update Realm to the latest version.
Also keep in mind if you open realm with Realm Browser that uses a newer version of realm it asks you to convert the file format. If you do that you can open this realm with older version of realm-cocoa.

Swift Realm, load the pre-populated database the right way?

I'm pretty new to ios development.
I follow this migration example to use pre-populated database and change the code a little bit
here is the final code I use on AppDelegate -> func application
let defaultPath = Realm.Configuration.defaultConfiguration.path!
let path = NSBundle.mainBundle().pathForResource("default", ofType: "realm")
if let bundledPath = path {
print("use pre-populated database")
do {
try NSFileManager.defaultManager().removeItemAtPath(defaultPath)
try NSFileManager.defaultManager().copyItemAtPath(bundledPath, toPath: defaultPath)
} catch {
print("remove")
print(error)
}
}
I'm testing this in a real device.
It works but according to the code logic, it'll always be reset to the pre-populated database. This is verified: the data is reset after app restart.
I tried moveItemAtPath instead of copyItemAtPath. permission error
I tried to delete the pre-populated database file after copy. permission error
I tried to use the pre-populated database file as the realm default configuration path. error occurs too.
In Swift 3.0, try this:
let bundlePath = Bundle.main.path(forResource: "default", ofType: "realm")
let destPath = Realm.Configuration.defaultConfiguration.fileURL?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: destPath!) {
//File exist, do nothing
//print(fileManager.fileExists(atPath: destPath!))
} else {
do {
//Copy file from bundle to Realm default path
try fileManager.copyItem(atPath: bundlePath!, toPath: destPath!)
} catch {
print("\n",error)
}
}
Yeah, your logic is correct. Every time this code gets executed, the default Realm file in the Documents directory is deleted and replaced with the static copy that came with the app bundle. This is done by design in the Realm sample code in order to demonstrate the migration process each time the app is launched.
If you only want that to happen one time, the easiest way to do it would be to check beforehand to see if a Realm file already exists at the default path, and then perform the copy only when it isn't already there. :)
let alreadyExists = NSFileManager.defaultManager().fileExistsAtPath(defaultPath)
if alreadyExists == false && let bundledPath = path {
print("use pre-populated database")
do {
try NSFileManager.defaultManager().removeItemAtPath(defaultPath)
try NSFileManager.defaultManager().copyItemAtPath(bundledPath, toPath: defaultPath)
} catch {
print("remove")
print(error)
}
}
try this
let realm_db_path = Realm.Configuration.defaultConfiguration.fileURL!
let bundle_realm_path = Bundle.main.url(forResource: "default", withExtension: "realm")!
if !FileManager.default.fileExists(atPath: realm_db_path.absoluteString){
do {
try FileManager.default.copyItem(at: bundle_realm_path, to: realm_db_path)
}catch let error {
NSLog(error as! String)
}