Implementing Background Upload/Download in Mobile Application
The difference between regular and background upload is fundamental. Regular works while user looks at screen. Background continues after app is minimized, screen is locked, and even after system forcibly terminates the process. Implementing background file transfer is one of those tasks where "almost works" and "works correctly" are separated by several subtle details.
iOS: URLSession with Background Configuration
On iOS, the only Apple-supported way for background transfer is URLSessionConfiguration.background. The system takes charge of the upload process; the app can be unloaded and restored upon completion.
let config = URLSessionConfiguration.background(withIdentifier: "com.app.bgTransfer")
config.sessionSendsLaunchEvents = true
config.isDiscretionary = false // for urgent transfers
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
Must implement in AppDelegate:
func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
backgroundSessionCompletionHandler = completionHandler
}
Call completionHandler in delegate's urlSessionDidFinishEvents(forBackgroundURLSession:) upon all tasks completion. If not called — iOS thinks app hung and kills it.
Typical error: create multiple URLSession with one identifier. On app recovery, iOS looks for existing session by identifier — duplicate creation loses tasks.
Large file upload should use uploadTask(with:fromFile:) not uploadTask(with:from:) — latter requires loading entire file into memory, guaranteeing process death on video > 100 MB.
Android: WorkManager vs DownloadManager vs ForegroundService
Three approaches, each for different scenarios:
| Approach | Suitable for | Drawbacks |
|---|---|---|
DownloadManager |
Public URL download | Download only, no fine-grained control |
WorkManager |
Upload/download with retry logic | 10 minute task limit |
ForegroundService |
Long transfers > 10 min | Requires notification, user can kill |
For uploading large files on Android, WorkManager + setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) is optimal. Worker.doWork() should return Result.retry() on network error; WorkManager restarts with backoff.
class UploadWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
val fileUri = inputData.getString("fileUri") ?: return Result.failure()
return try {
uploadFile(fileUri)
Result.success()
} catch (e: IOException) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
}
Set constraints when queuing:
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
Progress for Background Tasks
Notification with progress — via WorkManager setProgressAsync + observe via WorkManager.getInstance().getWorkInfoByIdLiveData(workId) in UI. On iOS, system URLSession shows progress in Control Center for downloads automatically. For custom progress — URLSessionDownloadDelegate.urlSession(_:downloadTask:didWriteData:).
Flutter: flutter_downloader solves background download for both platforms via native implementations. WorkManagerPlugin — for custom upload tasks. For React Native — react-native-background-upload (iOS) and custom HeadlessTask + WorkManager (Android).
Resumable uploads
For large files (500 MB+ video), implement resumable upload: file split into chunks (e.g., 5 MB), each uploaded separately, server assembles. On break — continue from last successful chunk. For AWS S3 — multipart upload API, for GCS — resumable upload sessions, for own backend — tus protocol (TUSKit on iOS, tus-android-client).
What's Included in the Work
Determine suitable mechanism per platform and requirements, implement progress in notifications, error handling with retry, UI integration. If chunked/resumable upload needed — design protocol with backend.
Timeline: 4–8 days including testing on real devices under network disruption.







