firebase-ios-sdk icon indicating copy to clipboard operation
firebase-ios-sdk copied to clipboard

FR: Allow background uploads and downloads

Open tonysung opened this issue 8 years ago • 53 comments

  • Firebase SDK version: 4.04
  • Firebase Product: storage

When an UploadTask/DownloadTask is created when the app is in background, it does leverage NSURLSession's background transfer and "discretionary" is set to true by default by the OS:

https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411552-discretionary

The result is that the task will be performed at the OS's discretion, like only if the device is WiFi connected.

However, the current API is limited in two aspect:

  1. Sometimes an app would like the task to proceed in background no matter what (with "discretionary" set to false). There's no way in Firebase API to specify this.

  2. Usually an app would create the UploadTask/DownloadTask when it is running in foreground, but expect the task to continue running in background and at the OS's discretion (i.e. NSURLSession created with [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier] and with "discretionary" set to true). This is useful for performing large files transfer without requiring the user to keep the app opened and simultaneously give discretion to the OS to help avoid consuming precious cellular bandwidth.

Hence, it'll be best if Firebase iOS can provide explicit API for an app to specify if OS discretion is allowed in an UploadTask/DownloadTask, and that the task can be run in background (even if the task is created when the app is in foreground).

tonysung avatar Jul 20 '17 09:07 tonysung

In the future, we plan on adding an explicit "upload/download in the background" as opposed to simply going with the OS. Adding a flag for this isn't particularly difficult: the hard part is fetching backgrounded events and re-attaching progress listeners (as the ones added before the app was backgrounded disappear once backgrounded). We have a good way to solve this, but (somewhat surprisingly) haven't had significant requests for this from developers.

The API might look something like:

// Enable background uploads
[FIRStorage setBackgroundTransfersEnabled: YES];

// Fetch backgrounded uploads and re-attach observers
[storage backgroundedUploadTasksWithBlock:^(UploadTask *task){
  [task observeStatus:FIRStorageTaskStatusProgress
                       handler:^(FIRStorageTaskSnapshot *snapshot) {
                         // A progress event occurred
                       }];
}];

Thoughts?

asciimike avatar Jul 26 '17 21:07 asciimike

Yes this is good.

Just two requests if this is going to be implemented:

  1. Preferably, background transfer can be enabled/disabled per task instead of globally.
  2. We should be able to configure the discretionary flag per task.

tonysung avatar Jul 27 '17 16:07 tonysung

How about getting rid of a global flag and creating individual methods for background uploads and downloads?

[ref putData:data inBackgroundWithCompletion:^(FIRStorageMetadata *metadata, NSError *error){
  // upload occurs in background
}];

[ref writeFile:localURL inBackgroundWithCompletion:^(NSURL *localFile, NSError *error) {
  // download occurs in background
}];

I think this ends up being more discoverable as well.

Note that we'd still have to offer the methods to fetch backgrounded tasks.

asciimike avatar Jul 27 '17 17:07 asciimike

This is definitely better.

tonysung avatar Aug 14 '17 16:08 tonysung

Are there any updates on this?

berkcoker avatar Jan 30 '18 01:01 berkcoker

@berkcoker unfortunately nothing, as a majority of dev efforts are going into Firestore at the moment. Happy to review a PR. GTMSessionFetcher supports background uploads/downloads (https://github.com/google/gtm-session-fetcher/blob/master/Source/GTMSessionFetcher.h#L79-L115), so it's not terribly difficult to add (honestly, we're more blocked on implementing it on Android and JS).

asciimike avatar Jan 30 '18 01:01 asciimike

@mcdonamp I too would like background uploads, especially support for offline handling that automatically resumes when the device is online. E.g. upload a photo for a post, then insert that post into Firestore when a device or app comes back online. Firebase does claim to support offline capabilities for most products, I was surprised to find the same ethos doesn't currently apply to Firebase Storage.

gbhall avatar Mar 19 '18 16:03 gbhall

I wonder if there are any updates on this? I'm currently using signed URLs, which I found easier to implement, but this feature feels like a no-brainer

berkcoker avatar Jul 09 '18 20:07 berkcoker

There are unfortunately no updates at this point. We will update this issue when this changes.

schmidt-sebastian avatar Jul 09 '18 20:07 schmidt-sebastian

Background uploads shouldn't be (only?) manually performed like suggested in https://github.com/firebase/firebase-ios-sdk/issues/147#issuecomment-318427481

IMO in most of the case, if an uploadTask is uploading while the user is leaving the app in background, a background upload should be automatically triggered (if enabled by a flag in the uploadTask).

Francescu avatar Jul 24 '18 09:07 Francescu

Found this issue when searching on how to set the NSURLSessionConfiguration.discretionary property to true, because the OS default is false. How would someone go about that?

I also got confused a bit at this point, because @tonysung mentions that true is the default, but in reality it is false, therefore exactly how he wanted it. This may have changed in the meantime, as he hasn't commented on this since.

toraritte avatar Aug 30 '18 16:08 toraritte

Really would love to see this implemented soon!

julsh avatar Nov 28 '18 17:11 julsh

Anyone know of any work arounds while it's not supported directly in the SDK?

ThoseGuysInTown avatar Dec 19 '18 21:12 ThoseGuysInTown

Sure, here's how I do it. Please let me know if you have any trouble.

class PhotoUploadManager {
  static var urlSessionIdentifier = "photoUploadsFromMainApp"  // Should be changed in app extensions.
  static let urlSession: URLSession = {
    let configuration = URLSessionConfiguration.background(withIdentifier: PhotoUploadManager.urlSessionIdentifier)
    configuration.sessionSendsLaunchEvents = false
    configuration.sharedContainerIdentifier = "my-suite-name"
    return URLSession(configuration: configuration)
  }()
  
  // ...
  
  // Example upload URL: https://firebasestorage.googleapis.com/v0/b/my-bucket-name/o?name=user-photos/someUserId/ios/photoKey.jpg
  func startUpload(fileUrl: URL, contentType: String, uploadUrl: URL) {
    Auth.auth().currentUser?.getIDToken() { token, error in
      if let error = error {
        print("ID token retrieval error: \(error.localizedDescription)")
        return
      }
      guard let token = token else {
        print("No token.")
        return
      }

      var urlRequest = URLRequest(url: uploadUrl)
      urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
      urlRequest.setValue(contentType, forHTTPHeaderField: "Content-Type")
      urlRequest.httpMethod = "POST"
      let uploadTask = PhotoUploadManager.urlSession.uploadTask(with: urlRequest, fromFile: fileUrl)
      uploadTask.resume()
    }
  }
}

I should note that the comment about app extensions and the sharedContainerIdentifier field are from code I wrote ages ago, when my app had an extension, so use at your own risk.

Ruberik avatar Dec 20 '18 17:12 Ruberik

Any update on this?

OscarGorog avatar Apr 06 '19 23:04 OscarGorog

@OkiRules Sorry, no update available. We're happy to review a PR.

paulb777 avatar Apr 07 '19 14:04 paulb777

Would also like to know if there's been any updates on this. @Ruberik how did you construct the upload url? I've tried to follow the example url you posted but doesn't seem to work.

mhle avatar Apr 19 '19 17:04 mhle

@mhle Let's say your bucket name is FOO, and you want the file to go in bucket FOO at the path BAR/BAZ/QUX.jpg. Then you'd upload it to: https://firebasestorage.googleapis.com/v0/b/FOO/o?name=BAR/BAZ/QUX.jpg

You'll need to set up your storage rules to allow uploads to that location.

Ruberik avatar Apr 19 '19 18:04 Ruberik

Thanks! that worked, is there a way to get notified about progress and completion inside the app? I tried conforming to URLSessionTaskDelegate and implement urlSession(session:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:) but didn't get any callback.

On Sat, Apr 20, 2019 at 3:32 AM Bartholomew Furrow [email protected] wrote:

@mhle https://github.com/mhle Let's say your bucket name is FOO, and you want the file to go in bucket FOO at the path BAR/BAZ/QUX.jpg. Then you'd upload it to: https://firebasestorage.googleapis.com/v0/b/FOO/o?name=BAR/BAZ/QUX.jpg

You'll need to set up your storage rules to allow uploads to that location.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/firebase/firebase-ios-sdk/issues/147#issuecomment-484980230, or mute the thread https://github.com/notifications/unsubscribe-auth/AAE4TM5GXXYPIYR7PTBHAW3PRIF23ANCNFSM4DTXORSA .

mhle avatar Apr 20 '19 02:04 mhle

nvm, I got it to work, many thanks for your help! I hope we'll get an official solution via the sdk soon.

mhle avatar Apr 20 '19 04:04 mhle

@mhle I also really hope it is part of the SDK soon, it would be very helpful to not have to make all the special calls... it should already be integrated through the SDK.

OscarGorog avatar Apr 20 '19 04:04 OscarGorog

We are discussing internally to see if we can allocate resource to this feature request. We are sorry that we don't have a better update at this time.

schmidt-sebastian avatar Apr 25 '19 17:04 schmidt-sebastian

@schmidt-sebastian any update?

OscarGorog avatar May 03 '19 05:05 OscarGorog

Hi @schmidt-sebastian @morganchen12, there are any updates? I would like to make it possible to use my app even when the user is offline/have a bad signal, the Real time database knows how to handle those situations, but using Firebase Storage making it unhelpful, 'cause at 80% of cases the user is uploading an image with the data that sent to the database.

Idomo avatar Jul 23 '19 22:07 Idomo

No progress on this issue unfortunately. You can work around it by generating a download URL and using NSURLSession's background transfer methods.

morganchen12 avatar Jul 23 '19 23:07 morganchen12

@morganchen12 Tried to generate download URL (using downloadURL(completion)) as you suggested, but seems like I can't do so if the file haven't uploaded yet. May you give me some way to get the download URL that the file would be uploaded to? (I need to save also the token that coming with this URL to be able to show the image in my app, didn't find a way to generate it manually).

What I'm actually trying to do is to use the image Data that I'm uploading with putData() as cache to the data that will come from the url (that would be exactly the same data), while I'll upload the data to Firebase (with NSURLSession, as you suggested), so I'll be able to save the url into Firebase Database (that supports offline mode and also is much faster 'cause it's just uploading text) and in the meanwhile show the cached image to the user.

Idomo avatar Jul 24 '19 17:07 Idomo

You should consider using a Cloud Function to do the upload and database update to guarantee update atomicity. The Cloud Function can be an https callable url that you transfer data to via NSURLSession.

morganchen12 avatar Jul 24 '19 17:07 morganchen12

@morganchen12 - If we use cloud function then in that case cloud function has limitations and it will not allow you to upload large file for example 25MB Video file.Best solution is to make it possible via upload/download in background.

shahmaulik avatar Jan 23 '20 06:01 shahmaulik

@shahmaulik That’s depends on your needs. I was able to make this workaround with Firebase functions ’cause I don’t upload files bigger than 5MB.

Yet, I didn’t close this issue ‘cause I think that they should add some build-in background upload option, also to support situations like you’ve described here.

Idomo avatar Jan 23 '20 08:01 Idomo

If this is a question of resource allocation, then where is the right place for developers like us to cast a "vote" so you can see which FRs are the ones we want the most? Once I was told to +1 something on some google bug reporter site but I couldn't figure out how to -- the bug reporter site was harder to use than the Firebase iOS SDK by a million miles 😄

xaphod avatar Jul 04 '20 15:07 xaphod