Converting local realms to synced realm - swift

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)
}
}
}
}

Related

How to share Realm (using swift) in extensions and AppDelegates

I am having some problems with RealmSwift with the Notification Service Extension. I hope everyone can help me or give me some advice. I sincerely thank you all! And my English is not good, let me know if there is something unclear.
First, I used FCM to push the message to my ios device, I need to save my push notification to the ios device (using Realm).
There are only two cases that have a way to save successfully.
1.app running
2.app is killed/background, and click to push banner notifications I hope I can do the app off/background and save it without having to click on the banner notification.
So I used the following method
1.Add Notification Service Extension in the project
2.Enable Background fetch/Remote notifications
3.Project and Notification Service Extension added to app groups
I know that Extension can't share data, so I changed the default path of Realm.
I added the following code to AppDelegates.swift and added the same code in NotificationService.swift.
like this
let sharedDirectory: URL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.myapp")! as URL
let sharedRealmURL = sharedDirectory.appendingPathComponent("db.realm")
Realm.Configuration.defaultConfiguration = Realm.Configuration(fileURL: sharedRealmURL)
After I added these codes, when the app is killed or the background, the push message can be successfully saved to Realm, but not every time, 10 push notifications will only store 5, and most Importantly, the functionality that could have been stored in the app runing or clicking on the banner notification failed.
(But it is stored, because if I cancel the code that changes the default path of Realm, the data that has not been saved will be displayed again)
My Realm class
import Foundation
import RealmSwift
class Order: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var name = ""
#objc dynamic var amount = ""
#objc dynamic var createDate = Date()
override static func primaryKey() -> String? {
return "id"
}
}
import UIKit
import RealmSwift
class RealmDao: NSObject {
static let shared = RealmDao()
private var realm: Realm!
private override init() {
self.realm = try! Realm()
}
func getRealmObject() -> Realm {
return self.realm
}
}
Part of my AppDelegate.swift
let realm = try! Realm()
let order: Order = Order()
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
////share Realm
let sharedDirectory: URL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.myapp")! as URL
let sharedRealmURL = sharedDirectory.appendingPathComponent("db.realm")
Realm.Configuration.defaultConfiguration = Realm.Configuration(fileURL: sharedRealmURL)
tainerURL(forSecurityApplicationGroupIdentifier: "group.myapp")
FirebaseApp.configure()
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
Messaging.messaging().delegate = self // For iOS 10 data message (sent via FCM)
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge], completionHandler: { granted, error in
if granted {
print("ok...")
} else {
print("no...")
}
})
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
return true
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let sharedDirectory: URL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.myapp")! as URL
let sharedRealmURL = sharedDirectory.appendingPathComponent("db.realm")
Realm.Configuration.defaultConfiguration = Realm.Configuration(fileURL: sharedRealmURL)
let userInfo = notification.request.content.userInfo
print("userInfo: \(userInfo)")
guard
let aps = userInfo[AnyHashable("aps")] as? NSDictionary,
let alert = aps["alert"] as? NSDictionary,
let body = alert["body"] as? String,
let title = alert["title"] as? String
else {
// handle any error here
return
}
print("Title: \(title) \nBody:\(body)")
order.name = title
order.amount = body
try! realm.write {
realm.add(order)
}
completionHandler([.badge, .sound, .alert])
}
All my NotificationService.swift
import UserNotifications
import Firebase
import UIKit
import RealmSwift
import Realm
class NotificationService: UNNotificationServiceExtension {
let realm = try! Realm()
let order: Order = Order()
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
let sharedDirectory: URL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.myapp")! as URL
let sharedRealmURL = sharedDirectory.appendingPathComponent("db.realm")
Realm.Configuration.defaultConfiguration = Realm.Configuration(fileURL: sharedRealmURL)
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
let message = request.content.userInfo
print("userInfo: \(message)")
guard
let aps = message[AnyHashable("aps")] as? NSDictionary,
let alert = aps["alert"] as? NSDictionary,
let title = alert["body"] as? String,
let body = alert["title"] as? String
else {
// handle any error here
return
}
print("Title: \(title) \nBody:\(body)")
order.name = title
order.amount = body
try! realm.write {
realm.add(order)
}
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.body = "\(bestAttemptContent.body) [fortest]"
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
I think my problem seems to be a problem with the default path change, because when I change the path, the push notification in the execution is saved, but the code that must delete the change path will be displayed! Is it what I missed that causes the original path to still exist (so the push notification in the app running will be stored in another data table?)

Swift MailCore2 GTMAppAuth Allowing Less Secure App Access

I am building a test app using MailCore2 and GTMAppAuth, and I bumped into this error:
A stable connection to the server could not be established.
I followed various SO posts, like this, this and this, and so I think my code should be correct. My implementation are as follows:
//At AppDelegate
var currentAuthorizationFlow: OIDAuthorizationFlowSession?
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
{
if currentAuthorizationFlow!.resumeAuthorizationFlow(with: url) {
self.currentAuthorizationFlow = nil
return true
}
return false
}
class ViewController: UIViewController {
let kIssuer = "https://accounts.google.com"
let kClientID = "\(MY_CLIENTID).apps.googleusercontent.com"
let kRedirectURI = "com.googleusercontent.apps\(MY_CLIENTID):/oauthredirect"
let kExampleAuthorizerKey = "googleOAuthCodingKey"
override func viewDidLoad() {
super.viewDidLoad()
authenticateGmail()
}
func authenticateGmail() {
let issuer = URL(string: kIssuer)!
let redirectURI = URL(string: kRedirectURI)!
let appDelegate = UIApplication.shared.delegate as! AppDelegate
OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { (configuration, error) in
//handleError
if let configuration = configuration {
let scopes = [OIDScopeOpenID, OIDScopeProfile, "https://mail.google.com/"]
let request = OIDAuthorizationRequest(configuration: configuration, clientId: self.kClientID, scopes: scopes, redirectURL: redirectURI, responseType: OIDResponseTypeCode, additionalParameters: nil)
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self, callback: { (authState, error) in
//handleError
if let authState = authState {
if let accessToken = authState.lastTokenResponse?.accessToken {
NSLog("Successfully authenticated: %#", accessToken)
self.fetchEmailsFromGmail(accessToken: accessToken)
}
}
})
}
}
}
func fetchEmailsFromGmail(accessToken: String) {
let session = MCOIMAPSession()
session.hostname = "imap.gmail.com"
session.port = 993
session.username = "XXX#\(COMPANY_DOMAIN)"
session.authType = .xoAuth2
session.connectionType = .TLS
session.oAuth2Token = accessToken
let fetchFolderOperation = session.fetchAllFoldersOperation()
fetchFolderOperation?.start({ (error, folders) in
//handleError
if let folders = folders, !folders.isEmpty {
print(folders)
}
})
}
My implementation allows me to authenticate successfully, ie there is an accessToken printed. But when it attempts to fetch folders, it throws:
A stable connection to the server could not be established.
The thing is, it is possible to solve this by doing both the following:
allowing less secure app access via the Googles Accounts page
follow this solution here
However, these methods are not really safe methods in my opinion. I do not recall needing to do any of these when I connect up my email to other email clients, ie Outlook etc, so I do not view the above as real solutions.
Could anyone advice? Is there something wrong with my code, or I really have to resort to doing both steps above?

Realm data doesn't show up on a physical device

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
}

How to handle optional Realm sync?

I have an app with local/offline Realm files/databases. I would like to add the option to log in to sync data across devices, and I figured Realm Object Server would be great for this. I know that when a user logs in, I will need to write a migration from a local realm to a synced realm. What do I do if a user decides to logout? What do I do if a user never logs in in the first place? Do I need to have two paths in my code (everywhere) to choose whether it should use the local or synced realm?
I use a RealmProvider class to get the realm instance.
class RealmProvider {
class func realm() -> Realm {
if let _ = NSClassFromString("XCTest") {
return try! Realm(configuration: Realm.Configuration(fileURL: nil, inMemoryIdentifier: "test", syncConfiguration: nil, encryptionKey: nil, readOnly: false, schemaVersion: 0, migrationBlock: nil, deleteRealmIfMigrationNeeded: true, objectTypes: nil))
} else {
let user = getUser()
guard let loggedInUser = user else {
do {
return try Realm()
} catch {
print(error)
}
return try! Realm()
}
// open up synced url to the username.
if let user = SyncUser.current {
let syncServerURL = URL(string: "realm://realm.myapp.com/~/\(loggedInUser.username.md5())")!
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: syncServerURL))
// Open the remote Realm
return try! Realm(configuration: config)
} else {
// login first.
let serverURL = URL(string: "http://realm.myapp.com")!
let token = getToken()
let customCredentials = SyncCredentials(customToken: token, provider: Provider("custom/myAuth"))
//
SyncUser.logIn(with: customCredentials,
server: serverURL) { user, _ in
if let user = user {
// can now open a synchronized Realm with this user
// Open Realm
let configuration = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: user, realmURL: URL(string: "realm://realm.myapp.com/~/\(loggedInUser.username.md5())")!)
)
_ = try! Realm(configuration: configuration)
}
}
}
return try! Realm()
}
}
}
To answer your question,
What do I do if a user decides to logout?
The RealmProvider will give you a local realm file.
What do I do if a user never logs in in the first place?
The data will still be stored in a local realm file, once the user logs in, you can open up a synced realm and run the migration.
PS: There is one catch, if the synced realm is being opened the first time, it will still return the local realm file. :/

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.