How to migrate a realm database to an app group? - ios

Really excited about the recent addition of sharing Realm data between apps and extensions. The documentation details how to set the default realm to the app group directory, I've got that working.
Here's what I'm stuck on -- what's the best way to transfer the old database to the new location in the app group?

Based on #segiddins comment, I decided to go with moving the old database to the app group using NSFileManager:
let fileManager = NSFileManager.defaultManager()
//Cache original realm path (documents directory)
let originalDefaultRealmPath = RLMRealm.defaultRealmPath()
//Generate new realm path based on app group
let appGroupURL: NSURL = fileManager.containerURLForSecurityApplicationGroupIdentifier("group.AppGroup")!
let realmPath = appGroupURL.path!.stringByAppendingPathComponent("default.realm")
//Moves the realm to the new location if it hasn't been done previously
if (fileManager.fileExistsAtPath(originalDefaultRealmPath) && !fileManager.fileExistsAtPath(realmPath)) {
var error: NSError?
fileManager.moveItemAtPath(originalDefaultRealmPath, toPath: realmPath, error: &error)
if (error != nil) {
println(error)
}
}
//Set the realm path to the new directory
RLMRealm.setDefaultRealmPath(realmPath)

Hope this will help other reader.
As discussed in https://github.com/realm/realm-cocoa/issues/4490, you can set app group path with below code and use File Manager to move existing db as mention above.
var config = Realm.Configuration()
config.fileURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)!.appendingPathComponent(dbFilename)
Realm.Configuration.defaultConfiguration = config

Related

In swift, is there a way to add new data from second Realm bundle, without overwriting any existing data in the current Default Realm?

Upon initial load of the app, the Bundled Realm (Realm1) is copied to the documents folder. Now that the bundled realm is set as the default realm, I am able update the bool property so that the table view can show marked and unmarked cells. However I am looking for a way to bundle a second realm (Realm2) with a later update, that will add new data to the existing default realm, but without overwriting the current default realm. I am currently working in swift 5 and Xcode 11.1, if that is helpful.
So far the only thing that I can think of is adding block of code to add new entries to the default realm. First the view will check to see what the count is of the realm, and if the count is the same as the original bundle, then it will add new data, if the count is equal to the initial bundle plus the new entries, then it will not add the new data again. I was hoping for a simpler solution that is cleaner in my opinion.
Ideally the end result would be a way to update the existing default realm, without overwriting the already edited content. Although I am rather new to using realm, any help in pointing me in the right direction for a solution would be greatly appreciated. Thanks.
Attached below is the current code I have implemented to load the default realm from the bundle.
let bundlePath = Bundle.main.path(forResource: "preloadedData", ofType: "realm")!
let defaultPath = Realm.Configuration.defaultConfiguration.fileURL!.path
let fileManager = FileManager.default
// Copy Realm on initial launch
if !fileManager.fileExists(atPath: defaultPath){
do {
try fileManager.copyItem(atPath: bundlePath, toPath: defaultPath)
print("Realm was copied")
} catch {
print("Realm was not coppied \(error)")
}
}
return true
Once you've created your default Realm on disk, if you want to read data from the bundled one, here's the code
let config = Realm.Configuration(
// Get the URL to the bundled file
fileURL: Bundle.main.url(forResource: "MyBundledData", withExtension: "realm"),
// Open the file in read-only mode as application bundles are not writeable
readOnly: true)
let realm = try! Realm(configuration: config)
and once you've read the data, you can switch back
var config = Realm.Configuration()
config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("\(some_realm_name).realm")
Realm.Configuration.defaultConfiguration = config
as far as not overwriting, ensure your objects use a unique primary key and when they are written, nothing will be overwritten as objects will a unique primary key will be added instead of overwriting.
class MyClass: Object {
#objc dynamic var my_primary_id = NSUUID().uuidString
I am adding an additional answer that's somewhat related to the first but also stands on it's own.
In a nutshell, once Realm connects to a data source, it will continue to use that data source as long as the objects are not released, even if the actual file is deleted.
The way around that is to encapsulate the Realm calls into an autorelease pool so that those objects can be released when the Realm is deleted.
Here’s an example:
This function adds a GameData object to the default.realm file.
func addAnObject() {
autoreleasepool {
let realm = try! Realm()
let testData = GameData()
testData.Scenario = "This is my scenario"
testData.Id = 1
try! realm.write {
realm.add(testData)
}
}
}
At this point, if you run the addAnObject code, your file will have a GameData object.
GameData {
Id = 1;
GameDate = (null);
Scenario = This is my scenario;
GameStarted = 0;
}
Then a function that delete’s the old realm, and copies the bundled realm to it’s place. This works because all of the interaction with Realm was enclosed in an autorelease pool so the objects can be released.
func createDefaultRealm() {
let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
let defaultParentURL = defaultURL.deletingLastPathComponent()
if let bundledRealmURL = self.bundleURL("default") {
do {
try FileManager.default.removeItem(at: defaultURL)
try FileManager.default.copyItem(at: bundledRealmURL, to: defaultURL)
} catch let error as NSError {
print(error.localizedDescription)
return
}
}
let migrationBlock : MigrationBlock = { migration, oldSchemaVersion in
//handle migration
}
Realm.Configuration.defaultConfiguration = Realm.Configuration(schemaVersion: 18, migrationBlock: migrationBlock)
print("Your default realm objects: \(try! Realm().objects(GameData.self))")
}
func bundleURL(_ name: String) -> URL? {
return Bundle.main.url(forResource: name, withExtension: "realm")
}
and please note that if you access Realm inside the class but outside an autorelease pool, Realm will refuse to 'let go' of it's objects.
Do do NOT do this!!
class ViewController: UIViewController {
var realm = try! Realm()

How to save PDF files from Firebase Storage into App Documents for future use?

I have connected my App with the Firebase Storage where my 19ea PDF files exists.
I would like to download those files and save them locally for future use.
Those PDF files will be used inside UIWebviews but they may need to be updated in time. Therefore, I have configured version control system with Firebase Database, so I will be able to push the newer versions when I update the files in the storage.
So, how I can save those files locally? (to a folder like: user/myapp/Documents/PDF etc?)
Also, how I can check if that folder contains any documents and how to delete them before downloading new files?
Here is what I have got so far.
I appreciate all the help.
// Firebase Storage Connection
static var refStorage:FIRStorageReference?
static var dataPDF = [NSData]()
func newDataDownload(){
// Compare Current Data Version with Online Data Version
if myFirebaseData.localDataVersion < myFirebaseData.onlineDataVersion {
// Set Firebase Storage Reference
myFirebaseData.refStorage = FIRStorage.storage().reference()
for i in 1...myFirebaseData.onlineTotalPDFCount {
// Create a reference to the file you want to download
let pulledPDF = FIRStorage.storage().reference().child("/PDF/\(i).pdf")
// Create local filesystem URL
let localURL = URL(string: "myApp/Documents/PDF/\(i)")!
pulledPDF.data(withMaxSize: myFirebaseData.maxPDFdownloadSize, completion: { (downPDF, err) in
if err == nil {
// Accessed the data
myFirebaseData.dataPDF.append(downPDF! as NSData)
print(myFirebaseData.dataPDF)
} else {
// If there is an error print it
print(err.debugDescription)
}
})
}
}
// If Data is successfully downloaded update Local Data Version
myFirebaseData.localDataVersion = myFirebaseData.onlineDataVersion
Use storageRef.write(toFile: completion:) (docs), like:
// Create a reference to the file you want to download
let pdfRef = storageRef.child("files/file.pdf")
// Create local filesystem URL
let localURL = URL(string: "path/to/local/file.pdf")!
// Download to the local filesystem
let downloadTask = pdfRef.write(toFile: localURL) { url, error in
if let error = error {
// Uh-oh, an error occurred!
} else {
// Local file URL for "path/to/local/file.pdf" is returned
}
}
Note that you can only write to /tmp and /Documents due to app sandboxing requirements (see Downloading Firebase Storage Files Device Issue for an example of how this fails otherwise).

How to create a pre bundled realm file and upload data to it?

I am new to Realm and I want to ship a pre bundled Realm file my app, however the realm documentation is unclear to me on how to actually create a .realm file and also upload the desired pre bundled data to it. I have not been able to find any solution via SO, Google, or Youtube. Step-by-step instructions would be very helpful.
We're still looking at ways to officially make generating pre-populated Realms more easy. It's definitely a desired feature for the Realm Browser, but due to the way that Realm files require a migration when changing the schema, it's somewhat tricky to incorporate into a UI. It's definitely something we want to improve down the line.
At the moment, the Browser has a converter in it that can perform minimal data import like CSV files.
The Realm documentation is saying that the easiest way for you to produce a pre-populated Realm specific for your apps needs is to build a separate macOS app whose sole role is to generate the Realm file, pre-populate the data and then expose the resulting Realm file so you can copy it to your app.
The operation to do this is just like any normal Realm interaction (try! Realm() is what causes the file to actually be initially created), except you manually interact with the Realm file on disk upon completion.
I'm working on an app for an iOS conference, and the schedule data for the event is going to be stored in a Realm that is pre-bundled with the app when it ships.
Since I thought creating an extra macOS app would be overkill for an iOS app, I instead used iOS unit tests that would generate the pre-bundled Realm from scratch every time it was executed.
class Tests: XCTestCase {
func testGenerateNewDefaultRealm() {
let sources = [conferences, sponsors, conferenceDays] as [Any]
XCTAssert(generateDefaultRealm(named: "MyConferenceRealm.realm", sources: sources))
}
}
extension Tests {
public func generateDefaultRealm(named name: String, sources: [Any]) -> Bool {
// Create and configure the Realm file we'll be writing to
let realm = generateRealm(named: name)
// Open a Realm write transaction
realm.beginWrite()
// Loop through each source and add it to Realm
for source in sources {
if let objectArray = source as? [Object] {
for object in objectArray {
realm.add(object)
}
}
else if let objectDictionary = source as? [String : Object] {
for (_, object) in objectDictionary {
realm.add(object)
}
}
}
// Commit the write transaction
do {
try realm.commitWrite()
}
catch let error {
print(error.localizedDescription)
return false
}
// Print the file location of the generated Realm
print("=====================================================================")
print(" ")
print("Successfully generated at")
print(realm.configuration.fileURL!.path)
print(" ")
print("=====================================================================")
return true
}
public func generateRealm(named name: String) -> Realm {
let exportPath = NSTemporaryDirectory()
let realmPath = exportPath.appending(name)
// Delete previous Realm file
if FileManager.default.fileExists(atPath: realmPath) {
try! FileManager.default.removeItem(atPath: realmPath)
}
// Create new Realm file at path
let objectTypes: [Object.Type] = [Conference.self, ConferenceDay.self, SessionBlock.self, Event.self, Presentation.self,
Session.self, Location.self, Speaker.self, Sponsor.self, Venue.self]
let configuration = Realm.Configuration(fileURL: URL(string: realmPath), objectTypes: objectTypes)
let realm = try! Realm(configuration: configuration)
return realm
}
}
Running this unit test will generate a new Realm file in the NSTemporaryDirectory() directory of the Mac, and then feed in a set of Array and Dictionary objects of un-persisted Realm Object instances that are created each time the test is run. The location of the final Realm is then printed in the console so I can grab it and move it to the app bundle.

sending a database/Realm using Multipeer Connectivity

I have an admin and a user app. Basically, I will build the database(realm) using Admin app then send the whole database to the user app.
Here is the path to my database:
let directory: NSURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.example.file")!
let fileURL = directory.URLByAppendingPathComponent("test.realm")
//What should I do to the path so I can send it via MultiPeerConnectivity : MCSession
try session.sendData("my Realm File To Send", toPeers: session.connectedPeers, withMode: .Reliable)
I'd recommend to write a compacted copy of your Realm file via writeCopyToURL(fileURL: _, encryptionKey: _). Read more about Realm's file size growth behavior to understand why that is a good idea.
You can then simply read the file contents via NSData(contentsOfURL: _).
let parentURL = fileURL.URLByDeletingLastPathComponent!
let compactedFileURL = parentURL.URLByAppendingPathComponent("compact.realm")
try! realm.writeCopyToURL(compactedFileURL)
let data = NSData(contentsOfURL: compactedFileURL)!

How to create a pre-prepared database and then load that data in my ios swift app?

I have a lot of data text in a couple of text files. How can I efficiently create a (Realm?) database with the data from those text files so I can add the database to my Xcode project and load the data into my app?
I have seen many tutorials on how to create Realm database with user entered data and then load it, but none with pre-made databases. I don't know if Realm is the right program to do this, but I looked really well.
I have downloaded the Realm Browser, but I could only view databases and couldn't find out how to create them easily.
EDIT:
I managed to create a database in Realm and put it in my xcode folder.
Then I try to load it like this, but let peoples doesn't contain the file's data, what am I missing:
let path = NSBundle.mainBundle().pathForResource("data", ofType: "realm")
var config = Realm.Configuration(fileURL: NSURL(fileURLWithPath: path!))
config.readOnly = true
let realm = try! Realm(configuration: config)
let peoples = realm.objects(Data)
Data is class which defines the schema:
class Data : Object {
dynamic var name = ""
dynamic var country = ""
dynamic var discription = ""
dynamic var image = ""
dynamic var cartoon = ""
dynamic var startYear = 0
dynamic var endYear = 0
}
An image of the realm file I'm trying to load:
Thanks for help!
Create a Sample Model:
final class ContentModel: Object {
dynamic var title = ""
dynamic var content = ""
dynamic var listName = ""
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
}
Create realm database:
let model = ContentModel()
model.id = 1
model.listName = "List Item 1"
model.title = "Title of content 1"
model.content = "Sample Text"
// Get the default Realm
let realm = try! Realm()
// Persist your data easily
try! realm.write {
realm.add(model)
}
Use this line to print the path to database:
print(Realm.Configuration.defaultConfiguration.fileURL!)
Now follow these steps (explained by realm)
Drag the new compacted copy of your Realm file to your final app’s Xcode Project Navigator.
Go to your app target’s build phases tab in Xcode and add the Realm file to the “Copy Bundle Resources” build phase.
At this point, your bundled Realm file will be accessible to your app. You can find its path by using NSBundle.main.pathForResource(_:ofType:).
Here's the code to get your bundled resource:
open class func getBundledRealm() -> Realm {
let config = Realm.Configuration(
// Get the URL to the bundled file
fileURL: Bundle.main.url(forResource: "default", withExtension: "realm"),
// Open the file in read-only mode as application bundles are not writeable
readOnly: true)
// Open the Realm with the configuration
let realm = try! Realm(configuration: config)
return realm
}
To Test your database:
let realm = RealmUtils.getBundledRealm()
// Read some data from the bundled Realm
let results = realm.objects(ContentModel.self)
for item in results {
print("Id: \(item.id)")
}
Realm Browser (as the name said) is just a browser.
for create pre-prepared database you should write some codes,
create empty database and insert data to it, then save it to Documents folder of simulator , comment insert code and copy generetated database from Documents folder, and add it as resource to your xCode project.
if your database static and you don't want to change anything on it simply just load your database with resource bundle path:
[[NSBundle mainBundle] pathForResource:#"nameOfFile" ofType:#"realm"];
but if you want to change some data you must copy it again to writable folder like Documents (for first time only)
I think what you are looking for is something like
https://github.com/Ahmed-Ali/JSONExport or JSONExportV in the Mac App Store
or
http://realmgenerator.eu/
If you already have your JSON, then these will generate the Realm Models that you can drop into your project. JSONExport supports way more languages and seems to work better for me with Swift. Just make sure you set the language to "Swift - Realm".

Resources