Implementing File Download in Mobile Application
File download splits into two fundamentally different scenarios: quick download of small resources (images, documents up to 10 MB) directly in memory, and loading large files (video, archives) with progress display and resume capability. Mixing approaches is a common mistake, expressed in OOM crashes or hanging progress without feedback.
Implementation by Platform
Android. For in-memory download — OkHttp or Retrofit with ResponseBody.byteStream(), write data to file in IO coroutine. For large files, system DownloadManager with status bar notification — user sees progress even after exiting app. Alternative — WorkManager + custom Worker, if more control over logic is needed.
Saving to Downloads folder on Android 10+: use MediaStore API for public files, getExternalFilesDir() for private. Attempt to write directly to /sdcard/Download/ without MediaStore on Android 11 will give SecurityException.
val request = DownloadManager.Request(Uri.parse(url))
.setTitle(fileName)
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
val downloadId = downloadManager.enqueue(request)
iOS. URLSession.downloadTask saves temporary file, then move it to FileManager.default.urls(for: .documentDirectory). For background downloads — URLSessionConfiguration.background(withIdentifier:) with URLSessionDownloadDelegate. Without background session, download interrupts when app goes to background.
let config = URLSessionConfiguration.background(withIdentifier: "com.app.download")
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
let task = session.downloadTask(with: URL(string: url)!)
task.resume()
Implement urlSession(_:downloadTask:didFinishDownloadingTo:) to move file and urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:) for progress.
Flutter: dio package with onReceiveProgress, saving via path_provider. For background download — flutter_downloader, wrapping native DownloadManager (Android) and URLSession (iOS).
Nuances Often Skipped
File may download partially due to connection loss. Resumable download via Range header (Range: bytes=1048576-) works only if server returns Accept-Ranges: bytes and Content-Range. If server doesn't support — download starts from scratch. Check backend behavior before implementing resume.
Also important to show real progress, not deterministic. If Content-Length header is absent — progress bar will "spin" without percentages.
What's Included in the Work
Choose approach for task (in-memory vs file, foreground vs background), implement progress, save to proper directory, handle network errors and clean temporary files.
Timeline: 1–3 days depending on resumable and background behavior requirements.







