Firebase Storage iOS put file completion handler called before upload finishes - ios

I was trying to upload videos to Firebase Storage, and when the upload finishes, I store the location of this video file to an object in the database, my code is as below:
Storage.storage().reference(withPath: mediaURL!).putFile(from: localCacheURL, metadata: nil, completion: { (metadata, error) in
if error != nil {
print("❗️failed to upload video")
} else {
print("a video is uploaded")
json = ["text": "", "image": "", "video": mediaURL!, "connections": []]
upload()
}
})
But I found the completion handler called before the upload actually finishes, I went to Firebase console and downloaded the uploaded file, it was unfinished.
Does anybody know why?

One for your reason for your problem may be that your cache file is somehow broken. To figure it out what's happening you need more information.
You can do that by observing the FIRFileStorageUploadTaskwhich is returned by putFile.
let uploadTask = Storage.storage().reference(withPath: mediaURL!).putFile(from: localCacheURL, metadata: nil, completion: { (metadata, error) in
if error != nil {
print("❗️failed to upload video")
} else {
print("a video is uploaded")
json = ["text": "", "image": "", "video": mediaURL!, "connections": []]
// unclear what this does
// looks strange if it's just a notification rename it into something like videoUploadCompleted
// if it needs the json stuff pass it as parameter to make it thread safe
upload()
}
})
uploadTask.observe(.progress) { snapshot in
print("\(snapshot.progress)")
}
It will give you something like:
<NSProgress: 0x604000123020> :
Parent: 0x0 / Fraction completed: 0.0062 / Completed: 1867836 of
301329576
You can also get more information about failures by using
uploadTask.observe(.failure) { snapshot in
if let error = snapshot.error as? NSError {
print ("Error : \(error)")
}
}
For more details about monitoring and error handling see also Upload Files on iOS Firebase Documentation

Related

Swift: Save image with file name using PHPhotoLibrary and PHAssetCreationRequest

I am trying to save an image to the user's photo library using PHPhotoLibrary and set the image file name at the time of saving suing the code below.
This is working the first time, but if I then try to save the same image again with a different file name, it saves with the same file name as before.
Is there something I need to add to let the system know to save a new version of the image with a new file name?
Thank you
PHPhotoLibrary.shared().performChanges ({
let assetType:PHAssetResourceType = .photo
let request:PHAssetCreationRequest = .forAsset()
let createOptions:PHAssetResourceCreationOptions = PHAssetResourceCreationOptions()
createOptions.originalFilename = "\(fileName)"
request.addResource(with: assetType, data: image.jpegData(compressionQuality: 1)!, options: createOptions)
}, completionHandler: { success, error in
if success == true && error == nil {
print("Success saving image")
} else {
print("Error saving image: \(error!.localizedDescription)")
}
})

How do you allow very large files to have time to upload to firebase before iOS terminates the task?

I have a video sharing app, and when you save a video to firebase storage it works perfectly for videos that are roughly 1 minute or shorter.
The problem that I am having, is when I try to post a longer video (1 min or greater) it never saves to firebase.
The only thing that I can think of is this error that I am getting, and this error only shows up about 30 seconds after I click the save button:
[BackgroundTask] Background Task 101 ("GTMSessionFetcher-firebasestorage.googleapis.com"), was created over 30 seconds ago. In applications running in the background, this creates a risk of termination. Remember to call UIApplication.endBackgroundTask(_:) for your task in a timely manner to avoid this.
Here is my code to save the video to firebase.
func saveMovie(path: String, file: String, url: URL) {
var backgroundTaskID: UIBackgroundTaskIdentifier?
// Perform the task on a background queue.
DispatchQueue.global().async {
// Request the task asseration and save the ID
backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Finish doing this task", expirationHandler: {
// End the task if time expires
UIApplication.shared.endBackgroundTask(backgroundTaskID!)
backgroundTaskID = UIBackgroundTaskIdentifier.invalid
})
// Send the data synchronously
do {
let movieData = try Data(contentsOf: url)
self.storage.child(path).child("\(file).m4v").putData(movieData)
} catch let error {
fatalError("Error saving movie in saveMovie func. \(error.localizedDescription)")
}
//End the task assertion
UIApplication.shared.endBackgroundTask(backgroundTaskID!)
backgroundTaskID = UIBackgroundTaskIdentifier.invalid
}
}
Any suggestions on how I can allow my video time to upload?
Finally figured this out after a long time...
All you have to do is use .putFile("FileURL") instead of .putdata("Data"). Firebase documentation says you should use putFile() instead of putData() when uploading large files.
But the hard part is for some reason you can't directly upload the movie URL that you get from the didFinishPickingMediaWithInfo function and firebase will just give you an error. So what I did instead was get the data of the movie, save the movie data to a path in the file manager, and use the file manager path URL to upload directly to firebase which worked for me.
//Save movie to Firestore
do {
// Convert movie to Data.
let movieData = try Data(contentsOf: movie)
// Get path so we can save movieData into fileManager and upload to firebase because movie URL does not work, but fileManager url does work.
guard let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent(postId!) else { print("Error saving to file manager in addPost func"); return }
do {
try movieData.write(to: path)
// Save the file manager url file to firebase storage
Storage.storage().reference().child("Videos").child("\(postId!).m4v").putFile(from: path, metadata: nil) { metadata, error in
if let error = error {
print("There was an error \(error.localizedDescription)")
} else {
print("Video successfully uploaded.")
}
// Delete video from filemanager because it would take up too much space to save all videos to file manager.
do {
try FileManager.default.removeItem(atPath: path.path)
} catch let error {
print("Error deleting from file manager in addPost func \(error.localizedDescription)")
}
}
} catch let error {
print("Error writing movieData to firebase \(error.localizedDescription)")
}
} catch let error {
print("There was an error adding video in addPost func \(error.localizedDescription)")
}

Swift: Uploading a file to Firebase Storage from a Share Extension

I am trying to upload an image to firebase storage from a share extension in iOS, I have authed and am communicating with the database but when I attempt to upload the file it fails straight away.
I have made sure that the code that I am using works by using it in my main app. I have also made sure that the file is being saved in the file manager prior to being uploaded correctly.
Here is the code for saving the file prior to the upload:
if let data = downsizeImage(image: image).jpegData(compressionQuality: 0.2) {
let fileManager = FileManager.default
let url = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.com.<DOMAIN>.imageShare")?.appendingPathComponent("ImageToSend.jpg")
do {
try data.write(to: url!)
}
catch {
print(error.localizedDescription)
}
}
Here is the code for the upload task:
let storageRef: StorageReference = Storage.storage().reference().child(storageLocation).child(UUID().uuidString)
var completed = false
var mediaUploadTask: StorageUploadTask?
let mediaTimeoutTask = DispatchWorkItem{ () in
if !completed {
mediaUploadTask?.cancel()
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 30, execute: mediaTimeoutTask)
mediaUploadTask = storageRef.putFile(from: mediaUrl, metadata: nil) {(metadata, error) in
completed = true
...
}
What should happen is the image is successfully uploaded and the function would continue as normal. What actually happens is the upload fails nearly straight away. Here is the error returned:
Printing description of error:
▿ Optional<Error>
- some : Error Domain=FIRStorageErrorDomain Code=-13000 "An unknown error occurred, please check the server response."
UserInfo={bucket=link-ages-55880.appspot.com,
_NSURLErrorFailingURLSessionTaskErrorKey=BackgroundUploadTask <AC5EADEA-6257-4C32-9454-17626156AA15>.<1>,
object=media/4qnjSBKysi79uCR3cTzf/04D22317-D2C0-4A5C-B032-4F37DB2C8F7A,
_NSURLErrorRelatedURLSessionTaskErrorKey=(
"BackgroundUploadTask <AC5EADEA-6257-4C32-9454-17626156AA15>.<1>"
),
NSLocalizedDescription=An unknown error occurred, please check the server response.,
ResponseErrorDomain=NSURLErrorDomain, ResponseErrorCode=-995}
If anyone has any idea what the problem might be, your ideas would be greatly appreciated. Thanks.
Here is a screenshot of the debugger:
I have found out what the problem was. Due to the way that iOS sandboxing works, calling:
storageRef.putFile(from: mediaUrl, metadata: nil, completion: {(metadata, error) in})
fails. More info Here: Original Answer.
Instead calling:
storageRef.putData(Data, metadata: nil, completion: {(metadata, error) in})
worked as intended.

getMetadata() not returning metadata from file in Firebase Storage iOS

I try to get metadata of files from Firebase Storage, in particular date of create (because I want to compare date of local file and date of Cloud file and change file, if needed). I use getMetadata { (metadata, error) in ...} method, but I don't get result in my completion. Control doesn't go to completion! My code is below
let metadataURL = Storage.storage().reference().child("tutorials/how_to/use_masking/cover.jpg")
metadataURL.getMetadata { (metadata, error) in
if let error = error {
print(error)
} else {
if let data = metadata {
let dict = data.dictionaryRepresentation()
print(dict)
}
}
}
And I want to say now, that image is really in this path, because I get this image with getData(...) method in the next step.

Firebase Storage: child() doesn't work with iOS App

I'm getting the following error when trying to download an image from my Firebase Storage:
Error Domain=FIRStorageErrorDomain Code=-13010 "Object 2xxxxxxx8/profile_pic does not exist."
(I obviously put the x's up there to mask private info.)
I'm adding a path reference to my Firebase Storage using the following code:
let storage = FIRStorage.storage()
let storageRef = storage.referenceForURL("gs://project-4xxxxxxxxxxxxx.appspot.com")
let profilePicReference = storageRef.child(signedInUser.uid + "/profile_pic")
I know the code above is good cause everything was working correctly: I could see a folder was added in my Storage space, and an image was uploaded into that folder - all directly from my iOS App.
The problems started when I manually deleted said folder from my Firebase Storage (I did this through the Firebase web portal) - just cause I wanted verify everything was working, so I deleted the folder to start fresh - expecting the code above would recreate it once I ran the App again - and since then I'm getting this error over and over again.
Really makes no sense.
Are there any quirks or issues with Firebase Storage? Some sort of caching that has to be addressed?
Any tips would be greatly appreciated!
Are there any quirks or issues with Firebase Storage? Some sort of
caching that has to be addressed?
An UploadTask executes asynchronously. If I try downloading an image immediately after uploading an image, I can reproduce your error. What's happening is that the download code executes before the image finishes uploading, producing the image-does-not-exist error. You can see that the download code executes too early by printing out some messages in the callbacks:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
That code produces the output:
[My Download Error]: ...."Object images/align_menu.tiff does not exist."...
and then after a few seconds I see the output:
[My Upload Success]:
[URL for download]: ...
which demonstrates that the download callback is executing before the upload callback. I can't quite figure out the details of why that happens--but obviously the callbacks are not added to a serial queue.*
To cure the asynchronous problem, you have several options:
1) Put the download code inside the callback for the upload code.
That way, the download won't start executing until after the image has successfully uploaded. After I did that, deleting the image using the Firebase Storage webpage before running the app had no deleterious effect on my upload/download, and the messages were output in the expected order:
[My Upload Success]:
[URL for download]: ...
[My Download Success]:
2) Attach a .Success observer to the uploadTask.
As described in the Firebase docs, in the Monitor Upload Progress section, you can get notified if the uploadTask successfully uploads the image:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
let observer = uploadTask.observeStatus(.Success) { (snapshot) -> Void in
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
3) Use Grand Central Dispatch to notify you when the upload is successful.
You don't have control over what queues the callbacks get added to (the Firebase method implementations decide that), but you can use Grand Central Dispatch to notify you when arbitrary code finishes executing. The following works for me:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
let myExecutionGroup = dispatch_group_create()
dispatch_group_enter(myExecutionGroup)
//Upload the image:
let _ = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
dispatch_group_leave(myExecutionGroup)
}
}
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
dispatch_group_notify(myExecutionGroup, queue) {
//This callback executes for every dispatch_group_leave().
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
* I tried putting a sleep(10) between the original upload code and download code, and that did not alleviate the problem. I thought that if the upload callback was executing on a background thread, then the upload callback would have time to complete while the main thread was sleeping, then after the sleep finished the download code would execute and the download callback would be added to a queue somewhere, then the download callback would execute. Because the sleep(10) didn't solve the problem, that meant the upload callback had to have been added to an execution queue for the main thread, and the sleep halted the main thread and anything in the queue from executing.
That leads me to believe that the upload and download callbacks are added to an asynchronous queue on the main thread (it's not a synchronous queue otherwise the callbacks would execute in order). I think an asynchronous queue on the main thread means that when there is dead time on the main thread, the tasks in the queue will execute, and you also get rapid switching between the various tasks when there is dead time in a particular task, like waiting for an HTTP response. For example, if there are two tasks in an asynchronous queue on the main thread, then there is rapid switching between the main thread, task1, and task2 whenever there is dead time in any one of them.

Resources