Implementing File Upload in Mobile Application
File upload from a mobile device looks like a simple task until the first crash on Android 10+ due to URI changes in FileProvider, or until the first rejected review in App Store due to incorrect media library permissions. Let's break down what needs to be done correctly.
Typical Implementation Problems
On Android, the main pain is working with content:// URI instead of direct file paths. Starting with Android 10 (API 29), direct filesystem access is restricted, and attempts to pass file:// path directly to Retrofit/OkHttp result in FileUriExposedException. The correct approach is to read the file via ContentResolver.openInputStream() and pass the stream, not the path.
On iOS, access to the photo library requires correct Info.plist: NSPhotoLibraryUsageDescription for iOS 13 and below, PHPickerViewController for iOS 14+ (doesn't require full photo album permissions). Using outdated UIImagePickerController with .photoLibrary in 2024 leads directly to reviewer comment from Apple.
Implementation
Multipart upload via Retrofit on Android:
@Multipart
@POST("upload")
suspend fun uploadFile(
@Part file: MultipartBody.Part,
@Part("description") description: RequestBody
): Response<UploadResponse>
// calling:
val requestBody = file.asRequestBody("image/*".toMediaTypeOrNull())
val part = MultipartBody.Part.createFormData("file", file.name, requestBody)
For large files (video, archives) — streaming transfer via RequestBody with overridden writeTo to avoid loading entire file into memory.
On iOS use URLSession.uploadTask(with:from:) or Alamofire:
AF.upload(
multipartFormData: { form in
form.append(fileURL, withName: "file")
},
to: "https://api.example.com/upload"
).uploadProgress { progress in
print(progress.fractionCompleted)
}.responseDecodable(of: UploadResponse.self) { response in
// handle
}
Flutter: dio package with FormData and MultipartFile.fromFile(). Progress via onSendProgress.
It's important to handle upload progress separately — show ProgressBar/LinearProgressIndicator with real percentages, not spinner. Cancel upload via CancellationToken (Kotlin) or Task (Swift/Alamofire).
What's Included in the Work
Choice of approach (multipart, presigned S3 URL, chunked upload) depends on file size and infrastructure. Implement file picker for platform, permission handling, progress display, retry on network error.
Timeline: 1–2 days for standard case, up to 4 days if chunked upload with resumable behavior is needed.







