Trouble with Firebase persistence feature - ios

I have a small app in which users can upload local files to the server.
I am trying to handle unexpected situation like losing Internet connection. I want to use persistence feature which I have declared in AppDelegate.
Database.database().isPersistenceEnabled = true
At first, I export file to the server and if it succeeds then database reference is created.
let uploadTask = ref.putData(contents as Data, metadata: myMetadata, completion: { (metadata, error) in
if error != nil {
...
} else {
DataService.instance.usersRef.observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in
...
}
}
)}
However, testing the solution (with turning airplane mode on and off) while exporting the file, the transfer is not restored. I am not sure if it's implementation issue or I'm missing key point of persistence feature.
Thanks in advance!

Related

Why does my realm notification token only trigger on my active device and not all devices with the open realm?

I'm newly learning iOS/swift programming and developing an application using MongoDB Realm and using Realm sync. I'm new to programming and realm, so please feel free to correct any terminology. My question is about listening for realm notifications, which I see referred to as change listeners and notification tokens. Regardless, here is the info:
My application has a list of locations with a status (confirmed/pending/cancelled). I open this list from my realm as a realm managed object and create my notification handler:
//This is called in it's own function, but assigns the locations
locations = publicRealm?.objects(Location.self)
//This is run after the function is called
self?.notificationToken = self?.locations!.observe { [weak self] (_) in
self?.tableView.reloadData()
print("Notification Token!!!")
I then populate my table view and let a user tap on a location, which passes the location and realm to another view controller where the user can update the status. That update is made in a separate view controller.
do{
try publicRealm?.write {
selectedLocation?.statusMessage = locationStatusTextField.text!
selectedLocation?.status = selectedStatus
}
}catch{
print("Error saving location data: \(error)")
}
At this point my notification token is successfully triggered on the device where I am making the location update. The change is shown immediately. However there is no notification token or realm refresh that happens on any other open devices that are showing the locations table view. They do not respond to the change, and will only respond to it if I force realm.refresh(). The change is showing in Atlas on MongoDB server, though.
I am testing on multiple simulators and my own personal phone as well, all in Xcode.
I'm very confused how my notification token can trigger on one device but not another.
When I first started the project it was a much simpler realm model and I could run two devices in simulator and updating one would immediately fire a change notification and cause the second device to show the correct notification.
I have since updated to a newer realm version and also made the realm model more complicated. Though for this purpose I am trying to keep it simple by doing all changes via swift and in one realm.
I also have realm custom user functions running and changing data but I think reading the docs I am realizing that those will not trigger a notification - I'm not sure if that's true though? I just know right now that if I change data in the DB via a user function no notifications are triggered anywhere - but if I do realm.refresh() then the change shows.
What is it that I am missing in how I am using these notifications?
***Updating information on Public Realm:
Save the realm:
var publicRealm:Realm?
Login as an anon user and then open the realm:
let configuration = user.configuration(partitionValue: "PUBLIC")
Realm.asyncOpen(configuration: configuration) { [weak self](result) in
DispatchQueue.main.async {
switch result {
case .failure(let error):
fatalError("Failed to open realm: \(error)")
case .success(let publicRealm):
self!.publicRealm = publicRealm
guard let syncConfiguration = self?.publicRealm?.configuration.syncConfiguration else {
fatalError("Sync configuration not found! Realm not opened with sync?")
}
It is after this realm opening that the locations are loaded and notification token is created.
I use a segue to pass the location object and realm to the next VC:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! UpdateLocationViewController
destinationVC.delegate = self
if let indexPath = tableView.indexPathForSelectedRow {
destinationVC.selectedLocation = locations?[indexPath.row]
}
if indexPathRow != nil {
destinationVC.selectedLocation = locations?[indexPathRow!]
}
destinationVC.publicRealm = self.publicRealm
}
Some notes:
original public realm opened by anon user
only a logged in user can click on a location... so in the 'UpdateLocation' VC that gets passed the public realm I am a logged in user. But, I'm just using the Dev mode of Realm to allow me to read/write however I like... and I am writing straight to that public realm to try and keep it simple. (I have a custom user function that writes to both public and the user's org realm, but I stopped trying to use the function for now)
I identify the object to update based on the passed in location object from the first VC
I needed to use try! when making my write call rather than just try. I updated my write blocks as such:
try! publicRealm?.write {
selectedLocation?.statusMessage = locationStatusTextField.text!
selectedLocation?.status = selectedStatus
}
Just wanted to follow up in case anyone finds this. Thank you!

NSPersistentCloudKitContainer bugs - doesn't sync objects changed offline

I'm testing my implementation of CoreData + CloudKit Sync, using NSPersistentCloudKitContainer and it seems to work fine some of the time. I fixed the problem with users switching iCloud on/off in the settings as recommended by Apple, but the problem I now have is that when I'm testing my app offline, when I'm back online with an internet connection, there's no syncing. I've got some code which does correctly trigger and detect the presence of an internet connection or not.. but still it doesn't seem that setting the container options to nil and then back to the cloudkit container gets it to sync. Here's my code so far:
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container : NSPersistentContainer?
container = NSPersistentCloudKitContainer(name: "Model")
let description = NSPersistentStoreDescription(url: applicationDocumentsDirectory()!.appendingPathComponent("Model.sqlite"))
description.setOption(true as NSNumber,forKey: NSPersistentHistoryTrackingKey)
//Check if user is first logged onto iCloud enabled/not - if not then set cloudkitcontainer options to nil to disable sync and use the local DB on device
// This was from an accepted answer here: https://forums.developer.apple.com/thread/118924
//NB This works and the code here is triggered because a switch on/off icloud in settings kills the app and makes it restart, so this code is triggered.
if FileManager.default.ubiquityIdentityToken != nil { //logged onto iCloud*/
description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.my.container")
} else {
description.cloudKitContainerOptions = nil;
}
//However, turning a device to airplane mode on/off won't kill the app to trigger the above code, so have to put that check in with checking when the device is on or offline:
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.my.container")
} else {
description.cloudKitContainerOptions = nil
}
}
//ensure migration------
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
//----------------------
container!.persistentStoreDescriptions = [description]
container!.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container!.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container!.viewContext.automaticallyMergesChangesFromParent = true
return container!
}()
Edit
I just tried this from device to device and it seems to work if one device is put into offline mode momentarily without the monitor instance activated (commented that bit of the code out to test). However if the device is put into airplane mode for a prolonged period of time it seems that CloudKit updates aren't sent and it's synced from the other device rather than the latest change from the device I'm on...

Realm Object Server refuses to recognize permission and loads objects extremely slow

I have a synchronized realm running on Realm Object Server. It is a global realm created by the admin user. I created another user on the server (not an admin) and used the admin user to grant read-only permissions to that user for that particular realm.
I can see the permission when I query the admin's management realm and the normal user's permission realm. However, when I connect to the global realm from my iOS app (Swift 3) with the normal user, the server returns error code 206: permission denied and closes the realm file.
The strange thing is that the app manages to download a few objects from the realm, and it also loads a few more objects (about 3) every time I relaunch it.
An even stranger thing is that when I launch the app with the admin user the same thing happens except it loads about 100 extra objects in every relaunch and it doesn't show the permission denied error.
Note: I'm getting these results on the iOS simulator and still haven't tried the app on an actual device.
UPDATE: I have tried the app on a physical device and the problem persists.
I'm using Realm Swift v2.10.1 on Xcode 8.3.3 and Realm Object Server v1.8.3 on a Linux Ubuntu 16.04.3 x64 machine.
EDIT:
Here is how I apply the permission:
let per = SyncPermissionValue(realmPath: "/realmname", userID: "userId", accessLevel: .read)
realmUser!.applyPermission(per, callback: { (error) in
print("PERMISSION ERROR: \(String(describing: error))")
if error == nil {
print("no error")
}
})
Here is how connect from my app. These methods are in a custom class which is used by the view controller requesting the objects from the realm.
private func logInToServer() {
SyncUser.logIn(with: .usernamePassword(username: "username", password: "pass", register: false), server: URL(string: "http://server IP:9080")!, onCompletion: { (user, error) in
var realmUser = user
if realmUser == nil {
realmUser = SyncUser.current
}
self.connectToDatabase(realmUser: realmUser!)
})
} // end logInToServer()
private func connectToDatabase(realmUser: SyncUser) {
DispatchQueue.main.async(execute: {
let configuration = Realm.Configuration(syncConfiguration: SyncConfiguration(user: realmUser, realmURL: URL(string: "realm://server IP/realmname")!))
self.realm = try! Realm(configuration: configuration)
// I initially found out that implementing a small delay gave the app enough time to download all objects even though the permission denied error was still there. But, this no longer works.
Timer.scheduledTimer(timeInterval: self.delay, target: self, selector: #selector(self.populateResults), userInfo: nil, repeats: false)
})
} // end connectToDatabase()
#objc private func populateResults() {
self.categories = self.realm.objects(BookCategory.self)
self.books = self.realm.objects(Book.self)
delegate?.didPopulteResults(categories, books) // The delegate is the view controller requesting the objects.
}

iOS Swift: Firebase Remote Config fetch values

Is there a way or delegate to catch the updated values while the app is running without terminating the app. I am using this method to fetch the values and update.
RemoteConfig.remoteConfig().fetch(withExpirationDuration: duration) { [weak self] (status, error) in
guard error == nil else {
print("Got an error fetching remote values \(error!)")
return
}
print ("Retrieved values from the cloud!")
RemoteConfig.remoteConfig().activateFetched()
guard let strongSelf = self else { return }
strongSelf.updateWithRomteConfigValues()
}
The activateFetched() call will make sure to get the latest update data (either from the defaults or the remote config) without needing to terminate the app.
I think the problem in your case come from the duration.
Try setting the duration to 0 (make sure to only do it if the developer mode is enabled )

Firebase Storage not working on iMessage extension

I am trying to integrate Firebase into an iMessage extension.
As a test, I am setting up Firebase and trying to save a local file to Firebase Storage in the viewDidAppear method. The Firebase Real-time Database works fine in the code below, only the storage part does not.
The exact same code works when done in a normal app (i.e. not a iMessage extension).
I get the following error message:
Error Domain=FIRStorageErrorDomain Code=-13000
"An unknown error occurred, please check the server response."
UserInfo={ResponseErrorDomain=NSURLErrorDomain, object=test.jpg,
bucket=myapp.appspot.com, ResponseErrorCode=-995,
`NSLocalizedDescription=An unknown error occurred, please check the server response.
I am doing the following:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
FIRApp.configure()
FIRAuth.auth()?.signInAnonymously { (user, error) in
guard let fileURL = Bundle.main.url(forResource: "test", withExtension:"jpg") else { return }
let storageRef = FIRStorage.storage().reference().child("test.jpg")
storageRef.putFile(fileURL, metadata: nil) { (metaData, error) in //produces error
if error != nil {
print(error.debugDescription)
}
}
FIRDatabase.database().reference().updateChildValues(["someKey" : "someValue"]) // works fine
}
}
I have a suspicion that iMessage extensions may get limited access to the file system (since they live in a different sandbox than the normal app), and thus getting the file wouldn't work. In this case putData works, but putFile doesn't. Solution: always upload and download in memory (putData and dataWithMaxSize:) vs the file system (putFile and writeFile).

Resources