Why does Firebase not support onDisconnect when app crashes? - swift

I am used that I have a little bug resulting in an app crash. Most likely the bug occurs when querying data from Firebase (yeah yeah, my fault). This is the first function that will be executed when the app launches:
let currentUser = FIRAuth.auth()?.currentUser?.uid
let con = self.gameRef!.childByAutoId()
self.usersKey = con.key
let values = ["username": "test", "score": 0, "userID": currentUser!] as [String : Any]
con.updateChildValues(values)
con.onDisconnectRemoveValue()
Then after this is set, I query for example an array from Firebase but I did set some values wrong what results in an app crash. The onDisconnected function is never executed. I waited for a whole day and my information is still showed in the realtime database. If this code is executed server side, how can it be that the server still thinks I am connected? Closing the app & turning on airplane mode will disconnect me correctly.

This happens because your app isn't shutting down properly. In order for that function to fire it has to be when the app shuts down and cleans up all of its connections. There is no way for it to do this when it crashes because it won't execute any more code than it already did.
You could wrap an exception handler around that part of the code and then execute what you need on that exception.

Related

Deleting, adding and updating Firestore documents when offline

I have been using the following code in my iOS Swift app:
class ProfileController
{
func remove(pid: String, completion: #escaping ErrorCompletionHandler)
{
guard let uid = self.uid else
{
completion(Errors.userIdentifierEmpty)
return
}
let db = Firestore.firestore()
let userDocument = db.collection("profiles").document(uid)
let collection = userDocument.collection("profiles")
let document = collection.document(pid)
document.delete()
{
error in
completion(error)
}
}
}
When the device is online, everything works fine. The completion handler of the deletecall is properly executed. However, when I am offline, I have noticed that the completion handler will not be executed as long as I am offline. As soon as I get back online, the completion handler will be called almost immediately.
I don't want to wait until the user is back online (which could take forever), so I changed the code a little bit and added a ListenerRegistration:
class ProfileController
{
func remove(pid: String, completion: #escaping ErrorCompletionHandler)
{
guard let uid = self.uid else
{
completion(Errors.userIdentifierEmpty)
return
}
let db = Firestore.firestore()
let userDocument = db.collection("profiles").document(uid)
let collection = userDocument.collection("profiles")
let document = collection.document(pid)
var listener: ListenerRegistration?
listener = document.addSnapshotListener(includeMetadataChanges: false)
{
snapshot, error in
listener?.remove() // Immediately remove the snapshot listener so we only receive one notification.
completion(error)
listener = nil
}
document.delete()
}
}
Although this works fine, I am not sure if this is the right way to go. I have read online that a snapshot listener can be used in real-time scenarios, which is not really what I am looking for (or what I need).
Is this the right approach or is there another (better) way? I only want to get notified once (thus I added the includeMetadataChanged property and set it to false). I also remove the ListenerRegistration once the completion handler was called once.
If the first approach does not work properly when being offline - what are the use cases of this approach? Before I change my entire codebase to use listeners, is there any way of executing the completion handler of the first approach when the device is offline?
TL;DR: The second implementation works fine, I am simply unsure if this is the proper way of receiving notifications when the device is offline.
If the first approach does not work properly when being offline - what are the use cases of this approach?
It depends on what you mean by "work properly". The behavior you're observing is exactly as intended. Write operations are not "complete" until they're registered at the server.
However, all writes (that are not transactions) are actually committed locally before they hit the server. These local changes will eventually be synchronized with the server at some point in the future, even if the app is killed and restarted. The change is not lost. You can count on the synchronization of the change to eventually hit the server as long as the user continues to launch the app - this is all you can expect. You can read more about offline persistence in the documentation.
If you need to know if a prior change was synchronized, there is no easy way to determine that if the app was killed and restarted. You could try to query for that data, if you know the IDs of the documents written, and you could check the metadata of the documents to find out the source of the data (cache or server). But in the end, you are really supposed to trust that changes will be synchronized with the server at the earliest convenience.
If your use case requires more granularity of information, please file a feature request with Firebase support.

Error when running coreml in the background: Error computing NN outputs error

I'm running an mlmodel that is coming from keras on an iPhone 6. The predictions often fails with the error Error computing NN outputs. Does anyone know what could be the cause and if there is anything I can do about it?
do {
return try model.prediction(input1: input)
} catch let err {
fatalError(err.localizedDescription) // Error computing NN outputs error
}
EDIT: I tried apple's sample project and that one works in the background so it seems it's specific to either our project or model type.
I got the same error myself at similar "seemingly random" times. A bit of debug tracing established that it was caused by the app sometimes trying to load its coreml model when it was sent to background, then crashing or freezing when reloaded into foreground.
The message Error computing NN outputs error was preceded by:
Execution of the command buffer was aborted due to an error during execution. Insufficient Permission (to submit GPU work from background) (IOAF code 6)
I didn't need (or want) the model to be used when the app was in background, so I detected when the app was going in / out of background, set a flag and used a guard statement before attempting to call the model.
Detect when going into background using applicationWillResignActive within the AppDelegate.swift file and set a Bool flag e.g. appInBackground = true. See this for more info: Detect iOS app entering background
Detect when app re-enters foreground using applicationDidBecomeActive in the same AppDelegate.swift file, and reset flag appInBackground = false
Then in the function where you call the model, just before calling model, use a statement such as:
guard appInBackground == false else { return } // new line to add
guard let model = try? VNCoreMLModel(for modelName.model) else { fatalError("could not load model") // original line to load model
I doubt this is the most elegant solution, but it worked for me.
I haven't established why the attempt to load the model in background only happens sometimes.
In the Apple example you link to, it looks like their app only ever calls the model in response to a user input, so it will never try to load the model when in background. Hence the difference in my case ... and possibly yours as well?
In the end it was enough for us to set the usesCPUOnly flag. Using the GPU in the background seems prohibited in iOS. Apple actually wrote about this in their documentation as well. To specify this flag we couldn't use the generated model class anymore but had to call the raw coreml classes instead. I can imagine this changing in a future version however. The snippet below is taken from the generated model class, but with the added MLPredictionOptions specified.
let options = MLPredictionOptions()
options.usesCPUOnly = true // Can't use GPU in the background
// Copied from from the generated model class
let input = model_input(input: mlMultiArray)
let output = try generatedModel.model.prediction(from: input, options: options)
let result = model_output(output: output.featureValue(for: "output")!.multiArrayValue!).output

adding data and querying from swift to firebase

I'm having issues in a query from swift to firebase. bellow is my sample JSON in firebase:
Lulibvi-d220
Contas
-KwlPQZTqVfNhHAsFyW5
Nome: "Assets"
Numero: "1"
-KwlGJLUTqVfnhYHAsFyW5
Nome: "Liabilities"
Numero: "2"
my code is the following:
let nome: String = "Liabilities"
let numero: String = "2"
ref = Database.database().reference()
ref.child("Contas").child("Assets").observeSingleEvent(of: .value) { (snapshot) in
let numero = (snapshot.value as? NSDictionary)?["Numero"] as? String
print (numero as Any)
}
when debugging, the debugger just jumps all the code after (snapshot) in and does not execute it.
what I'm I doing wrong?
thanks
tl;dr: To debug your asynchronous code in XCode, place a breakpoint on the first statement inside the completion handler.
Longer explanation:
When you observe a value from Firebase, it may take any amount of time to get that data. To prevent your program from being blocked during this time, the data is loaded from the Firebase database in the background while your code continues. Then when the data becomes available, Firebase calls your completion handler.
This pattern is known as asynchronous loading, and is common to pretty much any modern web API. But it can be incredibly hard to get used to.
One easy way to see what happens is to run the code with a few well-place logging statements:
ref = Database.database().reference()
print("Before attaching observer")
ref.child("Contas").child("Assets").observeSingleEvent(of: .value) { (snapshot) in
print("Inside completion handler")
}
print("After attaching observer")
This code will immediately print:
Before attaching observer
After attaching observer
And then after a few moments (depending on network speed and other factors):
Inside completion handler
While there are ways to make the code after the block wait for the data (see some of the links below for more on that), the more common way to deal with asynchronous loading is to reframe the question. Instead of trying to code "first get the data, then print it", frame your problem as "we start getting the data. whenever we get the data, we print it".
The way to model this into code is that you move all code that needs access to the data from Firebase into the completion handler of your observer. Your code already does that by having print (numero as Any) in there.
To debug your asynchronous code in XCode, place a breakpoint on the code inside the completion handler. That breakpoint will then be hit when the data comes back from Firebase.
A few questions that also deal with this behavior:
Swift: Wait for Firebase to load before return a function
How to make app wait until Firebase request is finished
Finish asynchronous task in Firebase with Swift
How to execute code directly after Firebase finishes downloading?
Is there a way to wait for a query to be finished in Firebase?

Xcode: +CoreDataProperties.swift issue

I'm designing an app which is going well but I had an issue a while ago whereby I had to create a new model for CoreData because I made alterations to the Entities. I'm up to the fourth version and I had another issue with the app and I cleaned it. Now, this is what I'm getting:
The 'deleted' Attribute is set to NSDate
but after I try to build it again I get the following error:
I thought if I made alterations to the Entity Xcode would pick that up and alter any files accordingly! But that doesn't seem to be the case!
I've tried deleting the +CoreDataProperties.swift files and the 'Shopping List' swift file, recreating the 'Shopping List' swift file, under a different class name, and trying to build it again but I get the same error. This tells me its a CoreData issue, not a Swift issue. Obviously I need the attribute as NSDate but I'm not sure where to go from here!
The only way I can get the app to build is to comment out the 'deleted' attribute in the +CoreDataProperties.swift file and it runs fine.
I have the app running on a test iPhone 6 and the last time I made changes to the Entity I lost all the data I entered manually on the phone because of errors. The only way to get the app back up and running was to delete the app off the phone and reinstall it. I seriously don't want to go down that route again because I have nearly 450 various records on the phone.
If I leave the 'deleted' Attribute commented out when its uploaded to the app store, will it fail to upload, and will it fail to work correctly if the upload is successful?
I'd rather sort the issue before trying!
What you need to do is called light weight migrations. In app delegate you need to tell the app to look for the new version and create presistence store
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.
*/// THIS IS THE CODE YOU NEED TO ADD
let container = NSPersistentContainer(name: "NAMEGOESHERE")
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
// End of new code
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)")
}
})
return container
This method worked for me. There are a number of tutorials out there on doing light weight migrations here and there is a stack overflow answer which is one I actually referred to when I had this problem here. By doing migrations your app shall be able to create a new persistance store and in that new store it ll change the attribute of deleted from Bool to NSdate as in the generated managedobject file. Hope this helps

Quickfix/J - Automatic re logon after Session disconnect("Done", false);

I have a Quickfix/J implementation and when I call this:
// from the sender/initiator
Session.lookupSession(sessionId).disconnect("Done", false);
I get the expected behaviour on the onLogout handlers
SenderFixEngine standalone logged off
Receiver Fix Engine logged OFF, next sender num : 2 next target num : 3
but I then immediately get the reverse happening automatically
Receiver Fix Engine logged ON next sender num : 3 next target num : 4
SenderFixEngine standalone logged on
The usual QF app design is not run-and-done, but an app that stays on during the duration of a session. Thus, if the session is broke for some reason, the engine immediately tries to reconnect so you don't miss anything.
If you really want to stop, you should call Initiator.stop(). Or better yet, just have your app terminate itself.
I honestly don't see any good reason to call disconnect().