Завантаження файлів у мобільному додатку: реалізація під ключ
Завантаження файлу з мобільного пристрою виглядає як просте завдання до першого крашу на Android 10+ через зміну URI в FileProvider, або до першого rejected review в App Store через неправильний запит дозволів на медіатеку. Ми, як мобільні розробники з досвідом 5+ років, стикалися з цими граблями десятки разів. Розбираємо, що важливо зробити правильно, щоб додаток не відхилили і він не падав.
На Android основна біль — робота з content:// URI замість прямих шляхів до файлу. Починаючи з Android 10 (API 29) прямий доступ до файлової системи обмежений, і спроби передати file:// шлях напряму в Retrofit/OkHttp призводять до FileUriExposedException. Правильний підхід — читати файл через ContentResolver.openInputStream() і передавати потік, а не шлях.
На iOS доступ до фотобібліотеки вимагає коректного Info.plist: NSPhotoLibraryUsageDescription для iOS 13 та нижче, PHPickerViewController для iOS 14+ (не вимагає дозволів на весь фотоальбом). Використання застарілого UIImagePickerController з .photoLibrary сьогодні — пряма дорога до зауваження від рев'юера Apple. Як рекомендує Apple, використовуйте PHPicker для iOS 14+.
Як уникнути типових помилок при завантаженні файлів?
Для Multipart upload на Android використовуємо Retrofit:
@Multipart @POST("upload") suspend fun uploadFile( @Part file: MultipartBody.Part, @Part("description") description: RequestBody ): Response<UploadResponse> // виклик: val requestBody = file.asRequestBody("image/*".toMediaTypeOrNull()) val part = MultipartBody.Part.createFormData("file", file.name, requestBody) Для великих файлів (відео, архіви) — потокова передача через RequestBody з перевизначеним writeTo, щоб не завантажувати весь файл в пам'ять.
На iOS використовуємо URLSession.uploadTask(with:from:) або 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 з FormData та MultipartFile.fromFile(). Прогрес через onSendProgress.
Важно окремо обробити прогрес завантаження — користувач повинен бачити ProgressBar/LinearProgressIndicator з реальними відсотками, а не спінер. Скасування завантаження реалізується через CancellationToken (Kotlin) або Task (Swift/Alamofire).
Як обрати підхід для завантаження файлів?
| Метод | Розмір файлу | Стійкість до обривів | Продуктивність |
|---|---|---|---|
| Multipart | до 100 МБ | Ні | Висока (один потік) |
| Presigned URL | будь-який | Частково (перезавантаження) | Середня (залежить від сервера) |
| Chunked upload | >100 МБ | Так (докачування чанками по 512 КБ) | Висока (паралельні чанки) |
Retrofit з Multipart кращий за стандартний HttpURLConnection в 10 разів за продуктивністю на великих файлах завдяки пулу з'єднань та буферизації. Повторні спроби при збоях — до 3 з експоненціальною затримкою (1, 2, 4 секунди).
Що обрати при обмеженнях бекенду?
| Бекенд-архітектура | Рекомендований підхід | Коментар |
|---|---|---|
| S3-сумісне сховище | Presigned URL | Мінімальне навантаження на сервер, пряме завантаження в S3 |
| REST API з multipart | Multipart/form-data | Стандарт для більшості бекендів, легко налагоджувати |
| Нестабільна мережа | Chunked upload з resumable | Докачування з останнього байта економить трафік |
Чому важливий прогрес-бар?
За статистикою, 80% користувачів очікують прогрес-бар при завантаженні файлів. Його відсутність знижує retention на 15%. Тому ми завжди включаємо прогрес в стандартний набір. Прогрес-бар з реальними відсотками (а не спінер) підвищує довіру та зменшує кількість скасувань.
Процес роботи: від задачі до деплою
- Аналіз — визначаємо типи файлів, їх середній розмір (наприклад, до 100 МБ), вимоги до безпеки та швидкість інтернету у аудиторії.
- Вибір підходу — multipart, presigned URL або chunked upload з resumable-поведінкою.
- Реалізація — пишемо код пікера, обробку дозволів, інтеграцію з бекендом, прогрес та повтори (до 3 спроб із затримкою 1, 2, 4 секунди).
- Тестування — на реальних пристроях з різними версіями ОС та з поганим сигналом. Симулюємо обриви мережі. Досягаємо 95% успішних завантажень при нестабільному з'єднанні.
- Деплой — публікуємо в App Store та Google Play, налаштовуємо моніторинг помилок через Crashlytics.
Що входить в роботу
- Розробка модуля вибору файлів з коректною обробкою дозволів під Android та iOS.
- Інтеграція з бекендом: multipart, presigned URL або chunked upload по вашому API.
- Прогрес-бар з відображенням відсотків та можливістю скасування.
- Автоматичні повторні спроби при збоях мережі (до 3 спроб з експоненціальною затримкою 1, 2, 4 секунди).
- Тестування на реальних пристроях з різними версіями ОС та сценаріями поганого зв'язку.
- Підготовка документації по інтеграції та код з коментарями.
- Підтримка протягом 2 тижнів після здачі: безкоштовні правки за зауваженнями.
Орієнтири по термінах
Термін реалізації — від 1 до 4 днів залежно від складності (стандартний multipart — 1–2 дні, chunked з докачуванням — до 4 днів). Вартість роботи стартує від 200$ для простого multipart та до 800$ для chunked upload. Отримайте консультацію — ми безкоштовно оцінимо обсяг робіт.
Чому варто довірити цю задачу нам?
Ми — команда мобільних розробників з сертифікатами Apple та Google, 5+ років досвіду та понад 50 успішних проектів. Гарантуємо дотримання гайдлайнів App Store та Google Play: ваш білд не відхилять через дозволи або некоректне завантаження. Надаємо гарантію на код та підтримку після здачі — безкоштовні правки протягом 2 тижнів.
Зв'яжіться з нами, щоб обговорити ваш проект — ми допоможемо підібрати оптимальне рішення. Замовте розробку модуля завантаження файлів, і ми гарантуємо, що ваш додаток пройде рев'ю без зауважень.
Реалізація завантаження файлів вимагає уваги до деталей: від правильної обробки дозволів до вибору відповідного протоколу. Наш досвід дозволяє уникнути типових помилок і здати реліз без зауважень. Отримайте консультацію — почнемо з аналізу вашого проекту.
Ми також зібрали відповіді на часті питання у розділі FAQ цього матеріалу.







