# Migration Guide This guide helps you upgrade between major versions of `@kesha-antonov/react-native-background-downloader`. ## Table of Contents - [Migration Guide](#migration-guide) - [Table of Contents](#table-of-contents) - [Migration Guide: v4.1.x → v4.2.0](#migration-guide-v41x--v420) - [Android Pause/Resume Now Supported](#android-pauseresume-now-supported) - [Before (v4.1.x)](#before-v41x) - [After (v4.2.0)](#after-v420) - [How It Works on Android](#how-it-works-on-android) - [New Android Permissions](#new-android-permissions) - [Google Play Console Declaration Required](#google-play-console-declaration-required) - [bytesTotal Returns -1 for Unknown Sizes](#bytestotal-returns--1-for-unknown-sizes) - [Before (v4.1.x)](#before-v41x-1) - [After (v4.2.0)](#after-v420-1) - [Why This Change?](#why-this-change) - [Migration Guide: v4.3.x → v4.4.0](#migration-guide-v43x--v440) - [iOS MMKV Dependency Change](#ios-mmkv-dependency-change) - [If you're using `react-native-mmkv`](#if-youre-using-react-native-mmkv) - [If you're NOT using `react-native-mmkv`](#if-youre-not-using-react-native-mmkv) - [Why This Change?](#why-this-change-1) - [Migration Guide: v4.0.x → v4.1.0](#migration-guide-v40x--v410) - [MMKV Dependency Change](#mmkv-dependency-change) - [If you're using `react-native-mmkv`](#if-youre-using-react-native-mmkv-1) - [If you're NOT using `react-native-mmkv`](#if-youre-not-using-react-native-mmkv-1) - [Why This Change?](#why-this-change-2) - [Migration Guide: v3.2.6 → v4.0.0](#migration-guide-v326--v400) - [Breaking Changes Overview](#breaking-changes-overview) - [Step-by-Step Migration](#step-by-step-migration) - [1. Update Import Path](#1-update-import-path) - [2. Rename API Methods](#2-rename-api-methods) - [`checkForExistingDownloads()` → `getExistingDownloadTasks()`](#checkforexistingdownloads--getexistingdownloadtasks) - [3. Update Download Creation](#3-update-download-creation) - [`download()` → `createDownloadTask()` + `.start()`](#download--createdownloadtask--start) - [Why This Change?](#why-this-change-3) - [4. Update Configuration](#4-update-configuration) - [New `progressMinBytes` Option](#new-progressminbytes-option) - [New Features](#new-features) - [`maxRedirects` Option (Android)](#maxredirects-option-android) - [Task States](#task-states) - [TypeScript Support](#typescript-support) - [Expo Projects](#expo-projects) - [Setup](#setup) - [Rebuild Required](#rebuild-required) - [Quick Migration Checklist](#quick-migration-checklist) - [Need Help?](#need-help) --- # Migration Guide: v4.1.x → v4.2.0 ## Android Pause/Resume Now Supported In v4.2.0, Android now fully supports `task.pause()` and `task.resume()` methods! You no longer need platform-specific code for pause/resume functionality. ### Before (v4.1.x) ```javascript import { Platform } from 'react-native' async function pauseDownload() { if (Platform.OS === 'ios') { await task.pause() } else { // Android didn't support pause - had to stop and restart console.log('Pause not supported on Android') } } ``` ### After (v4.2.0) ```javascript // Works on both iOS and Android! async function pauseDownload() { await task.pause() } async function resumeDownload() { await task.resume() } ``` ### How It Works on Android Android's `DownloadManager` doesn't natively support pause/resume. v4.2.0 implements this using: 1. **HTTP Range Headers:** When resuming, the library uses the `Range` header to request only the remaining bytes 2. **Foreground Service:** A `ResumableDownloadService` runs as a foreground service to ensure downloads continue in the background 3. **Temp Files:** Progress is saved to a `.tmp` file which is renamed upon completion **Server Requirement:** The server must support HTTP Range requests for resume to work correctly. If the server doesn't support range requests, the download will restart from the beginning. ### New Android Permissions The library now requires additional permissions that are automatically added: ```xml ``` These are declared in the library's `AndroidManifest.xml` and will be merged automatically. ### Google Play Console Declaration Required Because the library uses Foreground Service permissions, Google Play requires you to declare this in the Play Console when publishing your app. Without this declaration, you will see the following error: > "You must let us know whether your app uses any Foreground Service permissions." **To resolve this, complete these steps in the Google Play Console:** 1. Go to your app in the Google Play Console 2. Navigate to **App content** → **Foreground Service** 3. Select **Yes** when asked if your app uses Foreground Service permissions 4. Choose **Data sync** as the Foreground Service type 5. Select **Network processing** as the task 6. Provide a justification such as: > "This app downloads files in the background using a foreground service with a user-visible notification. The foreground service ensures downloads continue reliably when the app is in the background or when the device is under memory pressure. Users initiate downloads and can see download progress via the notification." **Note:** This is a Play Console compliance step only—no additional code changes are required. ## bytesTotal Returns -1 for Unknown Sizes When a server doesn't provide a `Content-Length` header, `bytesTotal` now returns `-1` instead of `0`. ### Before (v4.1.x) ```javascript .progress(({ bytesDownloaded, bytesTotal }) => { // bytesTotal was 0 when unknown, same as "zero bytes" const percent = bytesTotal > 0 ? (bytesDownloaded / bytesTotal) * 100 : 0 }) ``` ### After (v4.2.0) ```javascript .progress(({ bytesDownloaded, bytesTotal }) => { if (bytesTotal === -1) { // Server didn't provide Content-Length - show indeterminate progress console.log(`Downloaded ${bytesDownloaded} bytes (total unknown)`) } else if (bytesTotal > 0) { // Normal case - show percentage const percent = (bytesDownloaded / bytesTotal) * 100 console.log(`Downloaded ${percent.toFixed(1)}%`) } }) ``` ### Why This Change? - `-1` clearly indicates "unknown" vs `0` which could mean "zero bytes" - Allows apps to show indeterminate progress indicators when appropriate - Consistent with common conventions in download APIs --- # Migration Guide: v4.3.x → v4.4.0 ## iOS MMKV Dependency Change In v4.4.0, the MMKV dependency on iOS was removed from the podspec to prevent symbol conflicts when apps also use `react-native-mmkv`. ### If you're using `react-native-mmkv` **No action required!** The `react-native-mmkv` package already provides the required MMKV dependency (via MMKVCore pod), so everything will work automatically. ### If you're NOT using `react-native-mmkv` You must explicitly add the MMKV dependency to your `ios/Podfile`: ```ruby pod 'MMKV', '>= 1.0.0' ``` Then run `cd ios && pod install`. ### Why This Change? Previously, both this library and `react-native-mmkv` would include their own MMKV dependencies, causing symbol conflicts and crashes (EXC_BAD_ACCESS) on iOS. By removing the hard dependency, this library now relies on the app to provide the MMKV dependency, which: 1. Eliminates symbol conflicts with `react-native-mmkv` 2. Allows the app to control the MMKV version 3. Fixes crashes on iOS when both libraries are used together --- # Migration Guide: v4.0.x → v4.1.0 ## MMKV Dependency Change In v4.1.0, the MMKV dependency was changed from `implementation` to `compileOnly` to prevent duplicate class errors when apps also use `react-native-mmkv`. ### If you're using `react-native-mmkv` **No action required!** The `react-native-mmkv` package already provides the MMKV dependency, so everything will work automatically. ### If you're NOT using `react-native-mmkv` You must explicitly add the MMKV dependency to your app's `android/app/build.gradle`: ```gradle dependencies { // ... other dependencies implementation 'com.tencent:mmkv-shared:2.2.4' // or newer } ``` **Note:** MMKV 2.0.0+ is required for Android 15+ support (16KB memory page sizes). ### Why This Change? Previously, both this library and `react-native-mmkv` would each bundle their own copy of MMKV, causing Gradle to fail with "duplicate class" errors during build. By using `compileOnly`, this library now relies on the app to provide the MMKV dependency, which: 1. Eliminates duplicate class conflicts 2. Allows the app to control the MMKV version 3. Reduces APK size when `react-native-mmkv` is already included --- # Migration Guide: v3.2.6 → v4.0.0 This section helps you upgrade from v3.2.6 to v4.0.0. ## Breaking Changes Overview | v3.2.6 | v4.0.0 | Notes | |--------|--------|-------| | `checkForExistingDownloads()` | `getExistingDownloadTasks()` | Renamed for clarity | | `download(options)` | `createDownloadTask(options)` | Returns task in PENDING state | | Downloads start immediately | Must call `.start()` | Explicit control over when downloads begin | | No `progressMinBytes` | `progressMinBytes` in config | New option for hybrid progress reporting | --- ## Step-by-Step Migration ### 1. Update Import Path The internal source structure changed from `lib/` to `src/`, but the public API imports remain the same: ```javascript // No changes needed - imports work the same way import RNBackgroundDownloader from '@kesha-antonov/react-native-background-downloader' // Or named imports import { setConfig, createDownloadTask, getExistingDownloadTasks, directories } from '@kesha-antonov/react-native-background-downloader' ``` ### 2. Rename API Methods #### `checkForExistingDownloads()` → `getExistingDownloadTasks()` **Before (v3.2.6):** ```javascript const existingTasks = await RNBackgroundDownloader.checkForExistingDownloads() ``` **After (v4.0.0):** ```javascript const existingTasks = await RNBackgroundDownloader.getExistingDownloadTasks() ``` ### 3. Update Download Creation This is the most significant change. Downloads no longer start immediately. #### `download()` → `createDownloadTask()` + `.start()` **Before (v3.2.6):** ```javascript // Download started immediately upon calling download() const task = RNBackgroundDownloader.download({ id: 'my-download', url: 'https://example.com/file.zip', destination: `${RNBackgroundDownloader.directories.documents}/file.zip`, }) .begin(({ expectedBytes }) => { console.log(`Starting download, expected ${expectedBytes} bytes`) }) .progress(({ bytesDownloaded, bytesTotal }) => { console.log(`Progress: ${bytesDownloaded}/${bytesTotal}`) }) .done(() => { console.log('Download complete!') }) .error((error) => { console.log('Download error:', error) }) ``` **After (v4.0.0):** ```javascript // Create the task (in PENDING state) const task = createDownloadTask({ id: 'my-download', url: 'https://example.com/file.zip', destination: `${RNBackgroundDownloader.directories.documents}/file.zip`, }) .begin(({ expectedBytes }) => { console.log(`Starting download, expected ${expectedBytes} bytes`) }) .progress(({ bytesDownloaded, bytesTotal }) => { console.log(`Progress: ${bytesDownloaded}/${bytesTotal}`) }) .done(() => { console.log('Download complete!') }) .error((error) => { console.log('Download error:', error) }) // Explicitly start the download when ready task.start() ``` #### Why This Change? The explicit `.start()` pattern gives you more control: ```javascript // Set up multiple downloads first const tasks = urls.map((url, index) => createDownloadTask({ id: `download-${index}`, url, destination: `${directories.documents}/file-${index}`, }) .progress(({ bytesDownloaded, bytesTotal }) => { updateProgress(index, bytesDownloaded, bytesTotal) }) .done(() => handleComplete(index)) .error((err) => handleError(index, err)) ) // Start them all at once, or conditionally tasks.forEach(task => task.start()) // Or start based on some condition if (isWifiConnected) { tasks.forEach(task => task.start()) } ``` ### 4. Update Configuration #### New `progressMinBytes` Option **Before (v3.2.6):** ```javascript RNBackgroundDownloader.setConfig({ headers: { 'Authorization': 'Bearer token' }, progressInterval: 1000, isLogsEnabled: true, }) ``` **After (v4.0.0):** ```javascript RNBackgroundDownloader.setConfig({ headers: { 'Authorization': 'Bearer token' }, progressInterval: 1000, // Time-based interval (ms) progressMinBytes: 1024 * 1024, // NEW: Byte-based threshold (default: 1MB) isLogsEnabled: true, }) ``` The `progressMinBytes` option creates hybrid progress reporting - callbacks fire when EITHER: - The time interval has passed, OR - The minimum bytes have been downloaded Set `progressMinBytes: 0` to disable byte-based throttling and use time-only (like v3.2.6 behavior). --- ## New Features ### `maxRedirects` Option (Android) Configure maximum HTTP redirects for Android downloads: ```javascript const task = createDownloadTask({ id: 'my-download', url: 'https://example.com/file.zip', destination: `${directories.documents}/file.zip`, maxRedirects: 5, // NEW: Limit redirects (Android only) }) ``` ### Task States Tasks now have a clearer state machine: ```javascript const task = createDownloadTask({ ... }) console.log(task.state) // 'PENDING' - before start() task.start() // task.state will transition: 'PENDING' → 'DOWNLOADING' → 'DONE' // Other possible states: // - 'PAUSED' - after task.pause() // - 'FAILED' - on error // - 'STOPPED' - after task.stop() ``` ### TypeScript Support v4.0.0 includes full TypeScript definitions: ```typescript import RNBackgroundDownloader, { DownloadTask, Config, BeginHandlerParams, ProgressHandlerParams, DoneHandlerParams, ErrorHandlerParams, } from '@kesha-antonov/react-native-background-downloader' const task: DownloadTask = createDownloadTask({ id: 'typed-download', url: 'https://example.com/file.zip', destination: '/path/to/file.zip', }) .begin(({ expectedBytes, headers }: BeginHandlerParams) => { console.log(`Expected: ${expectedBytes}`) }) .progress(({ bytesDownloaded, bytesTotal }: ProgressHandlerParams) => { const percent = (bytesDownloaded / bytesTotal) * 100 }) .done(({ bytesDownloaded, bytesTotal }: DoneHandlerParams) => { console.log('Complete!') }) .error(({ error, errorCode }: ErrorHandlerParams) => { console.error(`Error ${errorCode}: ${error}`) }) task.start() ``` --- ## Expo Projects v4.0.0 includes an Expo config plugin for automatic iOS setup. ### Setup **app.json / app.config.js:** ```json { "expo": { "plugins": [ "@kesha-antonov/react-native-background-downloader" ] } } ``` The plugin automatically: - Adds the required `handleEventsForBackgroundURLSession` method to AppDelegate - Sets up the bridging header for Swift projects (RN 0.77+) ### Rebuild Required After adding the plugin, rebuild your app: ```bash npx expo prebuild --clean npx expo run:ios npx expo run:android ``` --- ## Quick Migration Checklist - [ ] Update package to v4.0.0 - [ ] Replace `checkForExistingDownloads()` with `getExistingDownloadTasks()` - [ ] Replace `download()` with `createDownloadTask()` - [ ] Add `.start()` call after setting up task handlers - [ ] (Optional) Configure `progressMinBytes` in `setConfig()` - [ ] (Optional) Use `maxRedirects` for Android if needed - [ ] (Expo) Add the config plugin to app.json - [ ] Rebuild your app (pod install for iOS, rebuild for Android) --- ## Need Help? If you encounter issues during migration, please: 1. Check the [README](./README.md) for updated documentation 2. Search [existing issues](https://github.com/kesha-antonov/react-native-background-downloader/issues) 3. Open a new issue with details about your setup