I am uploading small files to Firebase Storage, approximately 276KB a piece. The app can choose to do a small handful of these uploads per day from the background. The desired behavior is that whenever the app decides to upload the uploads can start in background, and finish while the app is in background without the user needing to interact.
My current code is this. This logic works if your device is in good conditions. But if your device is low power, or running on cellular data than the system seems to block the background upload.
public func uploadAudioToFirebase(fileName: String, audioPath: URL, completion: @escaping (Result<String, Error>) -> Void) {
let storage = Storage.storage()
let storageRef = storage.reference()
let audioName = fileName + ".m4a"
let audioRef = storageRef.child("audios/" + audioName)
let metadata = StorageMetadata()
metadata.contentType = "audio/x-m4a"
audioRef.putFile(from: audioPath, metadata: metadata) { metadata, error in
if let error = error {
completion(.failure(error))
return
}
guard let uploadMetadata = metadata else {
completion(.failure(NSError(domain: "MetadataError", code: -1, userInfo: [NSLocalizedDescriptionKey: "No metadata available"])))
return
}
audioRef.downloadURL { (url, error) in
guard let downloadURL = url else {
completion(.failure(error!))
return
}
completion(.success(downloadURL.absoluteString))
}
}
}
First I tried to create even smaller files to upload. I tested with file around 145KB in size, but this did not seem to change the behavior.
I thought to use the Firebase REST API and URLSessions
or NSURLSession
with discretionary
set to false, to take control of when the system chooses to upload. But according to the docs, if the upload starts from the background the system will automatically set discretionary
to true. Giving control back to the system to choose when the upload happens.
I have tried wrapping my Storage upload logic in a beginBackgroundTask
. But as far as I can tell this is having the same behavior as if it was not there.
public func uploadAudioToFirebase(fileName: String, audioPath: URL, completion: @escaping (Result<String, Error>) -> Void) {
var taskId: UIBackgroundTaskIdentifier = .invalid
taskId = UIApplication.shared.beginBackgroundTask(withName: "AudioUpload") {
UIApplication.shared.endBackgroundTask(taskId)
taskId = .invalid
}
let storage = Storage.storage()
let storageRef = storage.reference()
let audioName = fileName + ".m4a"
let audioRef = storageRef.child("audios/\(audioName)")
let metadata = StorageMetadata()
metadata.contentType = "audio/x-m4a"
audioRef.putFile(from: audioPath, metadata: metadata) { metadata, error in
if let error = error {
completion(.failure(error))
if taskId != .invalid {
UIApplication.shared.endBackgroundTask(taskId)
}
return
}
audioRef.downloadURL { (url, error) in
if let error = error {
completion(.failure(error))
} else if let downloadURL = url {
completion(.success(downloadURL.absoluteString))
}
if taskId != .invalid {
UIApplication.shared.endBackgroundTask(taskId)
}
}
}
}
My next idea was to use the BGTaskScheduler
with BGAppRefreshTask
. But I don't know if this will actually change the current behavior. As far as I can tell BGAppRefreshTask
will let the system choose when to periodically do small tasks. Which I think will be the same time that it is already choosing to, ie when it has WiFi and good battery life.
It makes it harder that I don't know how the Firebase SDK putFile
works under the hood. It seems to do some sort of background management already, it may just using the BGTaskScheduler
already.
I am at a roadblock now, I am not sure how to continue. I've read that you might be able to use Silent Push Notifications for something like this? This has limitation though, and I am unsure how well it works with a Firebase upload.
Is there a more straight forward way to force a small upload that I am missing?