How do I upload an image to Firebase Storage when the user terminates the app?
Here's my code:
firebaseRef.child("\(uid)/\(filePath)").put(data, metadata: metadata) { (metadata, error) in
// NOTE: This spot never reaches when user closes the app
if error != nil || metadata == nil {
log.error("Fail to store image in cloud storage")
} else {
// Do something with the metadata url
}
As you can see, the async completion block never completes if the user terminates the app and I'm not able to upload the user's image to Firebase Storage
How do I safeguard against user terminating the app after uploading the picture? I have a preliminary idea. Could I store the image in CoreData/NSUserDefaults temporarily, and retry the upload when the user opens the app again?
Good question. At present, on iOS, there's no way of persisting and restarting the upload or download if the app is backgrounded or killed. This feature exists on Android (since the Activity is reset on screen rotation, making this a far more common issue), and we're planning on making it available on iOS in the future.
Eventually, we want to simply make this a flag [FIRStorage enablePersistentUploads:YES] or similar, to automatically do this for you, so you don't have to think about it at all.
Related
I have a strange issue with firebase authentication. SigninCredential callback never gets called when I turn off and turn back on the internet. Only after a few hours(probably after 4 hours) callback gets triggered
val mCredential = FacebookAuthProvider.getCredential(mAccesstoken!!.token)
mFirebaseAuth.signInWithCredential(mCredential)
.addOnCompleteListener(this) { it ->
if (it.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCredential:success")
getCurrentUser(mFirebaseAuth.currentUser)
} else {
// If sign in fails, display a message to the user.// unable to retrieve details of the user
Log.w(
TAG,
"signInWithCredential:failure:: FirebaseAuthentication failed even though accesstoken present",
it.exception
)
Toast.makeText(
baseContext, "Authentication failed.",
Toast.LENGTH_SHORT
).show()
}
}
If I do a fresh install of the app and perform a sign in it works properly provided that the internet is turned on while I try to login into the app
First check for internet connection before executing the above code.
I figured out the issue. I added an active internet condition before performing a login that fixed sign-in problem. check the internet available on the device then only perform the login
I did face another issue that is addOnCompleteListener never gets called because I was waiting on the main thread instead I used executor to perform the task on a background thread
Scenario:
I am logged in with Firebase Auth, using the email provider. I change my user's photoURL and displayName using this Swift code:
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.photoURL = someURL
changeRequest?.displayName = someName
changeRequest?.commitChanges { (error) in
// ...
}
From then on, when I use Auth.auth().currentUser.photoURL and Auth.auth().currentUser.displayName to render the user's name and avatar, it all works as expected. No problem at all.
However, I was also logged in on another device, before I made the changes to photoURL and displayName. And on that device, the old user information is shown, even when killing and restarting the app. All of that while I am using the Auth.auth().addStateDidChangeListener API as well.
Only when I logout and log back in, is the user info refreshed. I guess it's using the locally cached data, but it's not fetching the fresh user info from the server or something?
Am I doing something wrong? Do I need to force a refresh when the app starts up or something?
Edit: I've tried again a few times and yeah it's 100% reproducible.
Log in on two devices. Output Auth.auth().currentUser.displayName somewhere on screen (or to the console).
On device A, change the displayName. When I refresh the UI, the new name is shown. When I restart the app, the new name is shown.
On device B, the old name is still shown, even after killing and restarting the app. Only when I logout and back in, is the new name finally shown.
In the end I created a new top level Users collection, where I am now storing the user profile data. And with a snapshot listener, updating info on one device also updates it on the others. Too bad this was necessary, but it works.
I have been trying for several months to implement an app that receives notifications whose content I want to be stored in the database of my application. I am using the Firebase Messagging plugin and I am receiving the notifications correctly, both in Foreground and in Background.
However, in the background I am unable to execute my callback without the need to press the notification explicitly by the user. Likewise, there is no way to increase the badge of the application when it is not open.
Now, I do not understand how applications like WhatsApp, Telegram and any other application that is not made by mere mortals work. How is it possible that they can get the data in backgroud, manage the badges, synchronize messages, etc? Being that, supposedly the services like Firebase are limited in background. Even with plugins like Background Mode my application is suspended by Android when the user closes it.
The code that I am currently using to handle notifications is the following:
// In foreground (WORKS)
this.firebaseMessaging.onMessage().subscribe((notificacion) => {
// Insert in DB
...
});
// In background (DOESN'T WORK)
this.firebaseMessaging.onBackgroundMessage().subscribe((notificacion) => {
// Insert in DB
...
});
What alternative is left? The only thing that occurs to me is to use notifications in Foreground and background only as a warning. So every time I open the application I'll have to call a message synchronization callback with the server (with badge management included).
If someone has some better way, I would greatly appreciate it if you throw a little light on the subject.
From already thank you very much
Ok, after more than a year I think it's time to close this question. I solved the problem this way:
Suppose I need to get a list of messages. This action is made by a function called getMessages()
When a new message is created in the backend, I send a push notification through Firebase service.
If the push notification is received in foreground I refresh call the method directly:
this.firebaseMessaging.onMessage().subscribe((notificacion) => {
getMessages();
});
If the push notification is received in background and the user taps on it there isn't a problem as it's supported by the plugin:
this.firebaseMessaging.onBackgroundMessage().subscribe((notificacion) => {
getMessages();
});
If the push notification is received in background but the user DOES NOT tap on it and opens the app in another way than the notification, we need to excecute the corresponing function when the app is opened in app.component.ts file:
this.platform.ready().then(() => {
getMessages();
// Extra stuff like this.statusBar.styleDefault(); or this.splashScreen.hide();
});
That way all the cases are considered! You should keep in mind that apps like Whatsapp or Telegram are developed in official technologies like Java or Kotlin which not only have official native plugins, also its code are excecuted natively and not in a limited browser as Cordova or Capacitor frameworks do
I am using firebase in an iOS-Swift project in which I have to enable offline support for uploading posts, in the post there is a picture and caption just like Instagram, so what I want is when user is offline and he/she wants to upload a post, his/her picture get saved in cache and when user comes online that photo get uploaded and give back a download url that we can use for saving posts-details it in database.
sample code is:
let photoIDString = UUID().uuidString
let storageRef = Storage.storage().reference(forURL: "storage ref URL").child("posts").child(photoIDString)
storageRef.putData(imageData, metadata: nil, completion: { (metadata, error) in
guard let metadata = metadata else {
return
}
if error != nil {
return
}
storageRef.downloadURL(completion: { ( url, error ) in
guard let downloadURL = url else {
return
}
let photoUrl = downloadURL.absoluteString
self.sendDataToDatabase(photoUrl: photoUrl)
})
}
)
I want to know what changes should I make in my code to provide the offline capability. Any code snippet will help more.
The problem is better view as re-send to server when there is an error.
For your offline case, you can check if the error return is a network error, or manually check network connection availability.
You can create a re-send array of object
e.g
var resendList : [YourObjectType]
// when failed to send to server
resendList.append(yourFailedObject)
And then, 2 solutions:
Check the network connectivity manually and reupload in when the app become active in func applicationDidBecomeActive(_ application: UIApplication) in appDelegate. For checking connectivity you can try the method here: Check for internet connection with Swift But this has a problem that, the user has to go out the app and back again with network connected
Keep track(listen to notification) on the connectivity change, using a suggestion method by https://stackoverflow.com/a/27310748/4919289 and reupload it to server
and loop through all objects in resendList and re-upload again.
I am not an iOS developer, but I can share logical flow and some references.
When user clicks on upload: Check if network is available?
if yes: upload the post.
if no:
save the post to app storage or offline database
set broadcast receiver to receive broadcast when device comes online. This link may be helpful.
upload post when device comes online.
If you are looking for solution that is offered by Firebase, you may find more details here.
Firebase offers you plenty of ways to do this in their documentation. https://firebase.google.com/docs/database/ios/offline-capabilities
When uploading to the firebase server, it will queue itself and wait until it has a internet connection again to upload. If this happens to timeout or you want to do it your own way just attempt to upload with a completionHandler on the setValue or updateChild functions - if not successfully and the error message is because of internet, add it to a local cache to the phone with the data and the path to the firebase server.
onLoad, attempt the same upload again until it succeeds, once it succeeds - clear the local cache.
I am trying to make sure Firebase has a connection before continuing to load the app. I'm using the code from Firebase's own code sample. I have placed it in the ViewDidLoad function on my home view controller:
let connectedRef = Database.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
if let connected = snapshot.value as? Bool, connected {
print("Connected")
} else {
print("Not connected")
// show alert here
}
})
The problem is that the above code always shows "Not Connected" before then showing "Connected". This is a problem because when the app is not connected, it's supposed to show an alert, and the alert will then fire every time the user opens the app.
Is this expected behavior? If so, is there a way around it?
How do I check for Firebase connectivity without Firebase returning that it's not connected first every time?
The behavior you're observing is expected. The Firebase SDK can't establish its connection immediately upon startup. There is always going to be some latency between launch and whenever a connection is first available.
Also, I don't think this strategy is a good idea, because mobile connections can be intermittent and flakey. Firebase can not possibly ensure that your app will retain a good connection even after it's first established. Your app will be easier to use if you assume that it doesn't have a connection all the time. Users have come to expect some level of offline usage, and Realtime Database supports that to some degree with offline data persistence.