@kesha-antonov/react-native-background-downloader 4.5.2 → 4.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,294 @@
1
+ # Changelog
2
+
3
+ ## v4.5.3
4
+
5
+ ### ✨ New Features
6
+
7
+ - **Android: Notification Grouping Mode (`summaryOnly`):** Added `mode` option to `NotificationsGroupingConfig`. Set `mode: 'summaryOnly'` to show only the summary notification for a group while individual download notifications are minimized (ultra-silent, no alert). Useful for keeping the notification shade clean during large batch downloads.
8
+ - `'individual'` (default) — all notifications shown, current behavior unchanged
9
+ - `'summaryOnly'` — only the group summary notification is shown with aggregate progress; individual notifications are invisible/silent
10
+ - **Android: Progress-Based Summary Notification:** In `summaryOnly` mode, the group summary notification now displays aggregate progress (total bytes downloaded / total bytes) across all downloads in the group.
11
+ - **Android: Auto-Remove Completed Downloads from Group:** Completed downloads are now automatically removed from notification groups, keeping the summary accurate.
12
+
13
+ ### 🏗️ Architecture Changes
14
+
15
+ - **JS: `NotificationGroupingMode` Type:** New exported type `'individual' | 'summaryOnly'` for the `mode` field in `NotificationsGroupingConfig`.
16
+ - **Android: Ultra-Silent Notification Channel:** Added `NOTIFICATION_CHANNEL_ULTRA_SILENT_ID` (`IMPORTANCE_MIN`) channel used for individual notifications in `summaryOnly` mode.
17
+ - **Android: `updateSummaryNotificationForGroup()`:** New method that dispatches to the correct summary update strategy based on grouping mode.
18
+
19
+ ---
20
+
21
+ ## v4.5.2
22
+
23
+ ### ✨ New Features
24
+
25
+ - **Update Headers on Paused Downloads:** Added ability to update headers (e.g., refresh auth tokens) on paused download tasks before resuming. Use `task.setDownloadParams()` to update headers while paused, then `task.resume()` to continue the download with new headers.
26
+ - **Use case:** User pauses a large download, returns hours/days later when auth token has expired. Now you can refresh the token and resume without restarting the download.
27
+ - **iOS:** Creates a fresh request with HTTP Range header and updated headers on resume
28
+ - **Android:** Updates both in-memory headers and persisted paused download state
29
+
30
+ ### 🏗️ Architecture Changes
31
+
32
+ - **JS: `setDownloadParams()` Now Async:** The `DownloadTask.setDownloadParams()` method is now async and returns `Promise<boolean>` indicating whether native headers were updated (true when task is paused).
33
+ - **Native: Added `updateTaskHeaders` Method:** New native method on iOS and Android to update headers for paused tasks.
34
+
35
+ ### 📚 Documentation
36
+
37
+ - Added "Updating headers on paused downloads" section to README
38
+ - Added `setDownloadParams()` method documentation to API.md
39
+ - Added iOS "Updating Headers on Paused Downloads" section to PLATFORM_NOTES.md
40
+
41
+ ---
42
+
43
+ ## v4.5.1
44
+
45
+ ### 🏗️ Architecture Changes
46
+
47
+ - **Android: UIDT Code Refactoring:** Extracted 980-line monolithic `UIDTDownloadJobService.kt` into modular components:
48
+ - `uidt/UIDTJobState.kt` - Data classes, constants, job registry
49
+ - `uidt/UIDTNotificationManager.kt` - All notification logic
50
+ - `uidt/UIDTJobManager.kt` - Job scheduling, cancel, pause, resume
51
+ - `utils/ProgressUtils.kt` - Progress calculation utilities
52
+ - Backward compatibility maintained via companion object delegates
53
+ - **Android: Removed Redundant jobScheduler.cancel():** In `pauseJob()`, removed unnecessary `jobScheduler.cancel()` after `jobFinished(params, false)` since `wantsReschedule=false` already tells the system the job is complete.
54
+
55
+ ---
56
+
57
+ ## v4.5.0
58
+
59
+ ### ✨ New Features
60
+
61
+ - **Android: Global Notification Configuration:** Added `showNotificationsEnabled` and `notificationsGrouping` config options for controlling UIDT notifications globally via `setConfig()`.
62
+ - **Android: Customizable Paused Notification Text:** Added `downloadPaused` to `NotificationTexts` interface for customizing the "Paused" notification text.
63
+ - **Android: Notification Update Throttling:** Notification updates are now synced with `progressInterval` for consistent UI/notification progress display.
64
+
65
+ ### 🐛 Bug Fixes
66
+
67
+ - **Android: Paused Downloads Continuing in Background:** Fixed paused UIDT downloads continuing to download in background after app restart. Now UIDT job is fully cancelled on pause, with a detached "Paused" notification shown.
68
+ - **Android: Duplicate Notifications After Resume:** Fixed duplicate notifications appearing when resuming downloads after app restart. Now uses stable notification IDs based on `configId.hashCode()`.
69
+ - **Android: Notification Not Updating After Resume:** Fixed notification stuck on old progress after resuming. Now resets notification timing on resume and shows correct progress immediately.
70
+ - **Android: Notification Updating While Paused:** Fixed notification progress updating even when download is paused.
71
+ - **Android: Stale Notifications on App Close:** All download notifications are now cancelled when the app closes via `invalidate()`.
72
+
73
+ ### 💥 Breaking Changes
74
+
75
+ - **Removed Per-Task Notification Options:** `isNotificationVisible` and `notificationTitle` removed from `DownloadParams` and `UploadParams`. Use global `setConfig({ showNotificationsEnabled, notificationsGrouping })` instead.
76
+
77
+ ### 🏗️ Architecture Changes
78
+
79
+ - **Android: Pause Behavior on Android 14+:** Complete redesign of pause/resume for User-Initiated Data Transfer (UIDT) jobs:
80
+ - **Problem:** UIDT jobs continue running in background even after app closes, causing "paused" downloads to secretly continue downloading.
81
+ - **Solution:** On pause, the UIDT job is properly terminated via `jobFinished(params, false)`. Download state is persisted to disk for resumption via HTTP Range headers.
82
+ - **UX:** A detached "Paused" notification (using `JOB_END_NOTIFICATION_POLICY_DETACH`) remains visible after job termination. On resume, a new UIDT job is created.
83
+ - **Follows Google's UIDT best practices:** State saved even without `onStopJob`, `jobFinished()` called on completion, notifications updated periodically with throttling.
84
+ - **Android: Separate Notification Channels:** Added separate channels for visible (`IMPORTANCE_LOW`) and silent (`IMPORTANCE_MIN`) notifications.
85
+
86
+ ### ✨ Improvements
87
+
88
+ - **Android: Cleaner Notifications:** Added `setOnlyAlertOnce(true)` and `setShowWhen(false)` to all notifications for less intrusive updates.
89
+ - **Example App: Persistent Notification Settings:** Show Notifications and Notification Grouping toggles are now persisted with MMKV.
90
+ - **Example App: Android 13+ Permission Request:** Added POST_NOTIFICATIONS permission request when enabling notifications.
91
+
92
+ ### 📚 Documentation
93
+
94
+ - Updated README with notification behavior during pause/resume
95
+ - Updated API.md with new notification configuration options
96
+ - Documented that notifications are removed when app closes
97
+
98
+ ---
99
+
100
+ ## v4.4.5
101
+
102
+ ### 🐛 Bug Fixes
103
+
104
+ - **Android: Stop Task Not Working on Android 14+:** Fixed `stopTask()` not actually stopping UIDT downloads on Android 14+. The JobScheduler job was cancelled but the underlying HTTP download continued. Now properly calls `resumableDownloader.cancel()` before removing from active jobs.
105
+ - **Android: Paused Tasks Not Persisting Across App Restarts:** Fixed paused UIDT downloads losing their state when the app was restarted. Added `getJobDownloadState()` to retrieve download state from active UIDT jobs and `savePausedDownloadState()` to properly persist pause state.
106
+ - **Android: ACCESS_NETWORK_STATE Permission:** Added missing permission required for JobScheduler network connectivity constraints on Android 14+.
107
+ - **Android: Downloaded Files List Showing Incomplete Files:** The "Downloaded Files" section in the example app now correctly filters out files that have active (non-DONE) download tasks, preventing incomplete files from appearing in the list.
108
+
109
+ ### 🧹 Code Cleanup
110
+
111
+ - **Removed Verbose Debug Logs:** Cleaned up extensive debug logging in `StorageManager`, `Downloader`, and `RNBackgroundDownloaderModuleImpl` that was cluttering production logs. Removed serialization/deserialization logs, verification reads, and per-item iteration logs while keeping error logging.
112
+ - **Simplified Kotlin Code:** Removed unnecessary `else` blocks containing only debug/warning logs from `pauseTask()` and `resumeTask()` methods for cleaner code.
113
+
114
+ ### ✨ Improvements
115
+
116
+ - **TypeScript: Added `destination` to Task Info:** The `destination` field is now returned from `getExistingDownloadTasks()` for paused downloads, allowing the app to know where the file will be saved.
117
+
118
+ ### 📚 Documentation
119
+
120
+ - Added `skipMmkvDependency` option documentation to README for Expo plugin
121
+
122
+ ---
123
+
124
+ ## v4.4.4
125
+
126
+ ### 🐛 Bug Fixes
127
+
128
+ - **Expo Plugin: Fixed TypeScript Types:** Corrected TypeScript type definitions in the Expo config plugin.
129
+
130
+ ---
131
+
132
+ ## v4.4.3
133
+
134
+ ### ✨ New Features
135
+
136
+ - **Expo Plugin: Auto-detect react-native-mmkv:** The Expo config plugin now automatically detects if `react-native-mmkv` is installed and skips adding the MMKV dependency to avoid duplicate class errors. Use `skipMmkvDependency: true` option to manually skip if needed.
137
+ - **Android: Version from package.json:** Android native code now reads the library version from `package.json` instead of hardcoding it.
138
+
139
+ ---
140
+
141
+ ## v4.4.2
142
+
143
+ ### 🐛 Bug Fixes
144
+
145
+ - **Kotlin 2.0 Compatibility:** Fixed compilation error with Kotlin 2.0 (React Native 0.77+) by updating `progressReporter` to use named parameter syntax. This ensures compatibility with both Kotlin 1.9 (RN 0.76) and Kotlin 2.x (RN 0.77+).
146
+
147
+ ---
148
+
149
+ ## v4.4.1
150
+
151
+ ### 🐛 Bug Fixes
152
+
153
+ - **Android: Paused Tasks Persistence:** Fixed paused downloads not being restored after app restart on Android. Added persistent storage for paused download state using MMKV/SharedPreferences.
154
+ - **iOS: Improved Pause/Resume Handling:** Better handling of pause/resume operations on app restarts for iOS.
155
+ - **Upload Task App Restart Recovery:** Fixed upload tasks not being recoverable after app restart (#143). Added persistent storage for upload task configurations.
156
+
157
+ ### ✨ Improvements
158
+
159
+ - **Example App:** Added task list display with animations and improved UI for managing downloads.
160
+
161
+ ### 📚 Documentation
162
+
163
+ - Updated README with clearer MMKV dependency instructions
164
+ - Added information about resuming tasks after app restarts
165
+ - Updated authors section
166
+
167
+ ---
168
+
169
+ ## v4.4.0
170
+
171
+ ### ✨ New Features
172
+
173
+ - **Android 16 UIDT Support:** Downloads are now automatically marked as User-Initiated Data Transfers on Android 16+ (API 36) to prevent thermal throttling and job quota restrictions. Downloads will continue reliably even under moderate thermal conditions (~40°C).
174
+
175
+ ### 🐛 Bug Fixes
176
+
177
+ - **iOS MMKV Conflict Fix:** Removed hard MMKV dependency from iOS podspec to prevent symbol conflicts with `react-native-mmkv`. Apps using `react-native-mmkv` no longer experience crashes (EXC_BAD_ACCESS) on iOS.
178
+
179
+ ### 📦 Dependencies & Infrastructure
180
+
181
+ - **New Android Permission:** Added `RUN_USER_INITIATED_JOBS` permission for Android 16+ UIDT support
182
+ - **iOS MMKV Dependency:** MMKV is no longer a hard dependency in the podspec. Apps not using `react-native-mmkv` must add `pod 'MMKV', '>= 1.0.0'` to their Podfile.
183
+
184
+ ### 📚 Documentation
185
+
186
+ - Added documentation about Android 16+ UIDT support in README
187
+ - Added iOS MMKV dependency section in README (similar to Android section)
188
+ - Added migration guide for iOS MMKV dependency change
189
+
190
+ ---
191
+
192
+ ## v4.2.0
193
+
194
+ > 📖 **Upgrading from v4.1.x?** See the [Migration Guide](./MIGRATION.md) for details on the new Android pause/resume functionality.
195
+
196
+ ### ✨ New Features
197
+
198
+ - **Android Pause/Resume Support:** Android now fully supports `task.pause()` and `task.resume()` methods using HTTP Range headers. Downloads can be paused and resumed just like on iOS.
199
+ - **Background Download Service:** Added `ResumableDownloadService` - a foreground service that ensures downloads continue even when the app is in the background or the screen is off.
200
+ - **WakeLock Support:** Downloads maintain a partial wake lock to prevent the device from sleeping during active downloads.
201
+ - **`bytesTotal` Unknown Size Handling:** When the server doesn't provide a `Content-Length` header, `bytesTotal` now returns `-1` instead of `0` to distinguish "unknown size" from "zero bytes".
202
+
203
+ ### 🐛 Bug Fixes
204
+
205
+ - **Android Pause Error:** Fixed `COULD_NOT_FIND` error when pausing downloads on Android by properly tracking pausing state
206
+ - **Temp File Cleanup:** Fixed temp files (`.tmp`) not being deleted when stopping or deleting paused downloads
207
+ - **Content-Length Handling:** Fixed progress percentage calculation when server doesn't provide Content-Length header
208
+
209
+ ### 📦 Dependencies & Infrastructure
210
+
211
+ - **New Android Permissions:** Added `FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_DATA_SYNC`, and `WAKE_LOCK` permissions for background download support
212
+ - **New Service:** Added `ResumableDownloadService` with `dataSync` foreground service type
213
+
214
+ ### 📚 Documentation
215
+
216
+ - Updated README to reflect Android pause/resume support
217
+ - Removed "iOS only" notes from pause/resume documentation
218
+ - Added documentation about `bytesTotal` returning `-1` for unknown sizes
219
+ - Updated Android DownloadManager Limitations section with new pause/resume implementation details
220
+
221
+ ---
222
+
223
+ ## v4.1.0
224
+
225
+ > 📖 **Upgrading from v4.0.x?** See the [Migration Guide](./MIGRATION.md) for the MMKV dependency change.
226
+
227
+ ### ⚠️ Breaking Changes
228
+
229
+ - **MMKV Dependency Changed to `compileOnly`:** The MMKV dependency is now `compileOnly` instead of `implementation` to avoid duplicate class errors when the app also uses `react-native-mmkv`. Apps not using `react-native-mmkv` must now explicitly add the MMKV dependency.
230
+
231
+ ### ✨ New Features
232
+
233
+ - **Expo Plugin Android Support:** The Expo config plugin now automatically adds the MMKV dependency on Android. Use `addMmkvDependency: false` option if you're already using `react-native-mmkv`.
234
+
235
+ ### 🐛 Bug Fixes
236
+
237
+ - **Duplicate Class Errors:** Fixed potential duplicate class errors when app uses both this library and `react-native-mmkv` by changing MMKV to `compileOnly` dependency
238
+
239
+ ### 📚 Documentation
240
+
241
+ - Added documentation for MMKV dependency requirements in README
242
+ - Updated Platform-Specific Limitations section with MMKV setup instructions
243
+ - Added Expo plugin options documentation
244
+
245
+ ---
246
+
247
+ ## v4.0.0
248
+
249
+ > 📖 **Upgrading from v3.x?** See the [Migration Guide](./MIGRATION.md) for detailed instructions.
250
+
251
+ ### ⚠️ Breaking Changes
252
+
253
+ - **API Renamed:** `checkForExistingDownloads()` → `getExistingDownloadTasks()` - Now returns a Promise with better naming
254
+ - **API Renamed:** `download()` → `createDownloadTask()` - Downloads now require explicit `.start()` call
255
+ - **Download Tasks Start Explicitly:** Tasks created with `createDownloadTask()` are now in `PENDING` state and must call `.start()` to begin downloading
256
+ - **New Config Option:** Added `progressMinBytes` to `setConfig()` - controls minimum bytes change before progress callback fires (default: 1MB)
257
+ - **Source Structure Changed:** Code moved from `lib/` to `src/` directory with proper TypeScript types
258
+
259
+ ### ✨ New Features
260
+
261
+ - **React Native New Architecture Support:** Full TurboModules support for both iOS and Android
262
+ - **Expo Config Plugin:** Added automatic iOS native code integration for Expo projects via `app.plugin.js`
263
+ - **Android Kotlin Migration:** All Java code converted to Kotlin
264
+ - **`maxRedirects` Option:** Configure maximum redirects for Android downloads (resolves #15)
265
+ - **`progressMinBytes` Option:** Hybrid progress reporting - callbacks fire based on time interval OR bytes downloaded
266
+ - **Android 15+ Support:** Added support for 16KB memory page sizes
267
+ - **Architecture Fallback:** Comprehensive x86/ARMv7 support with SharedPreferences fallback
268
+
269
+ ### 🐛 Bug Fixes
270
+
271
+ - **iOS Pause/Resume:** Fixed pause and resume functionality on iOS
272
+ - **RN 0.78+ Compatibility:** Fixed bridge checks with safe emitter checks
273
+ - **New Architecture Events:** Fixed `downloadBegin` and `downloadProgress` events emission
274
+ - **Android Background Downloads:** Fixed completed files not moving to destination
275
+ - **Progress Callback Unknown Total:** Fixed progress callback not firing when total bytes unknown
276
+ - **Android 12 MMKV Crash:** Added robust error handling
277
+ - **`checkForExistingDownloads` TypeError:** Fixed TypeError on Android with architecture fallback
278
+ - **Firebase Performance Compatibility:** Fixed `completeHandler` method compatibility on Android
279
+ - **Slow Connection Handling:** Better handling of slow-responding URLs with timeouts
280
+ - **Android OldArch Export:** Fixed module method export issue (#79)
281
+ - **MMKV Compatibility:** Support for react-native-mmkv 4+ with mmkv-shared dependency
282
+
283
+ ### 📦 Dependencies & Infrastructure
284
+
285
+ - **React Native:** Updated example app to RN 0.81.4
286
+ - **TypeScript:** Full TypeScript types in `src/types.ts`
287
+ - **iOS Native:** Converted from `.m` to `.mm` (Objective-C++)
288
+ - **Package Manager:** Switched to yarn as preferred package manager
289
+
290
+ ### 📚 Documentation
291
+
292
+ - Added documentation for `progressMinBytes` option
293
+ - Updated README for React Native 0.77+ instructions
294
+ - Improved Expo config plugin examples
package/README.md CHANGED
@@ -15,14 +15,14 @@
15
15
  <h1 align="center">React Native Background Downloader</h1>
16
16
 
17
17
  <p align="center">
18
- Download and upload large files on iOS & Android — even when your app is in the background or terminated.
18
+ Download and upload large files on iOS & Android — even when your app is in the background or terminated by the OS.
19
19
  </p>
20
20
 
21
21
  ---
22
22
 
23
23
  ## ✨ Features
24
24
 
25
- - 📥 **Background Downloads** - Downloads continue even when app is in background or terminated
25
+ - 📥 **Background Downloads** - Downloads continue even when app is in background or terminated by the OS
26
26
  - 📤 **Background Uploads** - Upload files reliably in the background
27
27
  - ⏸️ **Pause/Resume** - Full pause and resume support on both iOS and Android
28
28
  - 🔄 **Re-attach to Downloads** - Reconnect to ongoing downloads after app restart
@@ -37,7 +37,7 @@
37
37
  **The Problem:** Standard network requests in React Native are tied to your app's lifecycle. When the user switches to another app or the OS terminates your app to free memory, your downloads stop. For small files this is fine, but for large files (videos, podcasts, documents) this creates a frustrating user experience.
38
38
 
39
39
  **The Solution:** Both iOS and Android provide system-level APIs for background file transfers:
40
- - **iOS:** [`NSURLSession`](https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background) - handles downloads in a separate process, continuing even after your app is terminated
40
+ - **iOS:** [`NSURLSession`](https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background) - handles downloads in a separate process, continuing even after your app is terminated by the OS. Note: if the user explicitly force-kills the app via the App Switcher, iOS cancels all background tasks — this is an iOS system limitation that cannot be overridden
41
41
  - **Android:** A combination of [`DownloadManager`](https://developer.android.com/reference/android/app/DownloadManager) for system-managed downloads, [Foreground Services](https://developer.android.com/develop/background-work/services/foreground-services) for pause/resume support, and [MMKV](https://github.com/Tencent/MMKV) for persistent state storage
42
42
 
43
43
  **The Challenge:** These APIs are powerful but complex. Downloads run in a separate process, so your app might restart from scratch while downloads are still in progress. Keeping your UI in sync with background downloads requires careful state management.
@@ -53,6 +53,7 @@
53
53
  - [📦 Installation](#-installation)
54
54
  - [Expo Projects](#expo-projects)
55
55
  - [Bare React Native Projects](#bare-react-native-projects)
56
+ - [MMKV version comparison](#mmkv-version-comparison)
56
57
  - [🚀 Usage](#-usage)
57
58
  - [Downloading a file](#downloading-a-file)
58
59
  - [Re-Attaching to background tasks](#re-attaching-to-background-tasks)
@@ -113,7 +114,7 @@ export default {
113
114
  expo: {
114
115
  plugins: [
115
116
  ["@kesha-antonov/react-native-background-downloader", {
116
- mmkvVersion: "2.2.4", // Customize MMKV version on Android
117
+ mmkvVersion: "1.3.16", // Customize MMKV version on Android
117
118
  skipMmkvDependency: true // Skip if you want to add MMKV manually
118
119
  }]
119
120
  ]
@@ -123,8 +124,8 @@ export default {
123
124
 
124
125
  | Option | Type | Default | Description |
125
126
  |--------|------|---------|-------------|
126
- | `mmkvVersion` | string | `'2.2.4'` | The version of [MMKV](https://github.com/Tencent/MMKV/releases) to use on Android. |
127
- | `skipMmkvDependency` | boolean | `false` | Skip adding MMKV dependency. Set to `true` if you're using [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv) to avoid duplicate class errors. The plugin auto-detects `react-native-mmkv` but you can use this option to explicitly skip. |
127
+ | `mmkvVersion` | string | `'1.3.16'` | The version of [MMKV](https://github.com/Tencent/MMKV/releases) to use on Android. See [MMKV version comparison](#mmkv-version-comparison) for details. |
128
+ | `skipMmkvDependency` | boolean | `false` | Skip adding MMKV dependency. Set to `true` if you're using [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv) to avoid duplicate class errors. The plugin auto-detects `react-native-mmkv` but you can use this option to explicitly skip. See [MMKV version comparison](#mmkv-version-comparison). |
128
129
 
129
130
  </details>
130
131
 
@@ -214,11 +215,29 @@ Add MMKV to your `android/app/build.gradle`:
214
215
 
215
216
  ```gradle
216
217
  dependencies {
217
- implementation 'com.tencent:mmkv-shared:2.2.4'
218
+ implementation 'com.tencent:mmkv-shared:1.3.16'
218
219
  }
219
220
  ```
220
221
 
221
- > **Note:** If you're already using [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv) in your project, skip this step — it already includes MMKV.
222
+ > **Note:** If you're already using [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv) in your project, skip this step — it already includes MMKV. Note that `react-native-mmkv` v4.x uses [Margelo's fork of MMKV](https://github.com/margelo/MMKV) (`io.github.zhongwuzw:mmkv`) which re-adds armeabi-v7a (32-bit ARM) support that was dropped in the official MMKV 2.x release.
223
+
224
+ > **⚠️ armeabi-v7a (32-bit ARM) users:** MMKV 2.x dropped 32-bit ABI support (since v2.0.0). If you need armeabi-v7a support and get a CMake error like `No compatible library found for //mmkv/mmkv`, use the MMKV 1.3.x LTS series instead — it supports both armeabi-v7a **and** 16KB page sizes (since v1.3.14):
225
+ > ```gradle
226
+ > dependencies {
227
+ > implementation 'com.tencent:mmkv-shared:1.3.16'
228
+ > }
229
+ > ```
230
+
231
+ #### MMKV version comparison
232
+
233
+ | Dependency | armeabi-v7a (32-bit) | arm64-v8a | 16KB page size | Recommended for |
234
+ |---|:---:|:---:|:---:|---|
235
+ | `com.tencent:mmkv-shared:1.3.16` (**default**) | ✅ | ✅ | ✅ (since 1.3.14) | Most apps — broadest device coverage |
236
+ | `com.tencent:mmkv-shared:2.x` | ❌ | ✅ | ✅ | 64-bit only apps (no legacy devices) |
237
+ | `io.github.zhongwuzw:mmkv:2.3.0` ([Margelo fork](https://github.com/margelo/MMKV)) | ✅ | ✅ | ✅ | Used automatically by `react-native-mmkv` v4.x — skip manual dependency |
238
+ | `react-native-mmkv` (already in project) | ✅ | ✅ | ✅ | If you already use `react-native-mmkv` — skip Step 4 entirely |
239
+
240
+ **TL;DR:** Use the default `1.3.16`. If you already have `react-native-mmkv` in your project, skip Step 4.
222
241
 
223
242
  ## 🚀 Usage
224
243
 
@@ -636,9 +655,92 @@ setConfig({
636
655
  })
637
656
  ```
638
657
 
658
+ **Notification grouping modes:**
659
+
660
+ When downloading many files (e.g., thousands of photos), you can use the `mode` option to control how notifications are displayed:
661
+
662
+ | Mode | Description |
663
+ |------|-------------|
664
+ | `'individual'` | Default. Shows all individual notifications grouped together with a summary |
665
+ | `'summaryOnly'` | Shows only ONE notification with real-time aggregate progress (e.g., "45% - 5 files"). Individual UIDT notifications are collapsed into an invisible group. Ideal for bulk downloads |
666
+
667
+ ```javascript
668
+ import { setConfig } from '@kesha-antonov/react-native-background-downloader'
669
+
670
+ // For bulk downloads (e.g., syncing thousands of photos)
671
+ // Use 'summaryOnly' mode to show only ONE notification with aggregate progress
672
+ setConfig({
673
+ showNotificationsEnabled: true,
674
+ notificationsGrouping: {
675
+ enabled: true,
676
+ mode: 'summaryOnly', // Only show summary notification with progress bar
677
+ texts: {
678
+ groupTitle: 'Syncing Photos',
679
+ groupText: '{count} files downloading',
680
+ },
681
+ },
682
+ })
683
+ ```
684
+
685
+ **Example: Batch downloading multiple files with grouped notifications:**
686
+
687
+ ```javascript
688
+ import { setConfig, createDownloadTask, directories } from '@kesha-antonov/react-native-background-downloader'
689
+
690
+ // Configure for bulk downloads with single summary notification
691
+ setConfig({
692
+ showNotificationsEnabled: true,
693
+ notificationsGrouping: {
694
+ enabled: true,
695
+ mode: 'summaryOnly',
696
+ texts: {
697
+ groupTitle: 'Photo Sync',
698
+ groupText: '{count} photos downloading',
699
+ },
700
+ },
701
+ })
702
+
703
+ // Download multiple files - they all share ONE notification with aggregate progress
704
+ const photos = [
705
+ { id: 'photo-1', url: 'https://example.com/photo1.jpg' },
706
+ { id: 'photo-2', url: 'https://example.com/photo2.jpg' },
707
+ { id: 'photo-3', url: 'https://example.com/photo3.jpg' },
708
+ // ... potentially thousands of files
709
+ ]
710
+
711
+ const GROUP_ID = 'photo-sync-batch'
712
+
713
+ for (const photo of photos) {
714
+ const task = createDownloadTask({
715
+ id: photo.id,
716
+ url: photo.url,
717
+ destination: `${directories.documents}/${photo.id}.jpg`,
718
+ metadata: {
719
+ groupId: GROUP_ID, // Required for grouping
720
+ groupName: 'Photo Sync', // Displayed in notification title
721
+ },
722
+ })
723
+ .progress(({ bytesDownloaded, bytesTotal }) => {
724
+ // Progress tracked per file, notification shows aggregate progress
725
+ })
726
+ .done(() => {
727
+ console.log(`Downloaded ${photo.id}`)
728
+ })
729
+ .error(({ error }) => {
730
+ console.error(`Failed ${photo.id}:`, error)
731
+ })
732
+
733
+ task.start()
734
+ }
735
+
736
+ // User sees: ONE notification showing "45% - 3 files" with progress bar
737
+ // instead of 3 separate notifications cluttering the notification shade
738
+ // Notification automatically disappears when all downloads complete
739
+ ```
740
+
639
741
  **Grouping downloads by category:**
640
742
 
641
- When notification grouping is enabled, you can group related downloads (e.g., by album, playlist, podcast) by passing `groupId` and `groupName` in the task metadata:
743
+ When notification grouping is enabled, you can group related downloads (e.g., by album, playlist, podcast) by passing `groupId` and `groupName` in the task `metadata`:
642
744
 
643
745
  ```javascript
644
746
  import { createDownloadTask, directories } from '@kesha-antonov/react-native-background-downloader'
@@ -650,7 +752,7 @@ const task = createDownloadTask({
650
752
  destination: `${directories.documents}/track01.mp3`,
651
753
  metadata: {
652
754
  groupId: 'album-summer-hits', // Unique identifier for the group
653
- groupName: 'Summer Hits 2024', // Display name in notification
755
+ groupName: 'Summer Hits 2024', // Display name in notification title
654
756
  },
655
757
  })
656
758
 
@@ -674,6 +776,7 @@ This ensures users always see the download status without unexpected background
674
776
  |--------|------|---------|-------------|
675
777
  | `showNotificationsEnabled` | boolean | `false` | Show full download notifications. When `false`, creates minimal silent notifications (UIDT jobs require a notification, but it will be barely visible). This is a top-level config option. |
676
778
  | `notificationsGrouping.enabled` | boolean | `false` | Enable notification grouping |
779
+ | `notificationsGrouping.mode` | `'individual'` \| `'summaryOnly'` | `'individual'` | Notification display mode. Use `'summaryOnly'` for bulk downloads to show only ONE notification with real-time aggregate progress |
677
780
  | `notificationsGrouping.texts` | object | See below | Customizable notification texts |
678
781
 
679
782
  **Notification texts (`notificationsGrouping.texts`):**
@@ -696,6 +799,7 @@ This ensures users always see the download status without unexpected background
696
799
  - This feature only affects Android 14+ (API 34) where UIDT jobs are used
697
800
  - On older Android versions, the standard DownloadManager notifications are shown
698
801
  - iOS uses system download notifications and doesn't support custom grouping
802
+ - **Use `mode: 'summaryOnly'` when downloading many files** to prevent notification spam - shows ONE notification with aggregate progress bar that updates in real-time
699
803
 
700
804
  </details>
701
805
 
@@ -751,6 +855,8 @@ This can happen with slow-responding servers. The library automatically adds kee
751
855
  <summary><strong>Duplicate class errors with react-native-mmkv (Android)</strong></summary>
752
856
 
753
857
  If you're using `react-native-mmkv`, you don't need to add the MMKV dependency manually - it's already included. The library uses `compileOnly` to avoid conflicts.
858
+
859
+ `react-native-mmkv` v4.x uses [Margelo's fork of MMKV](https://github.com/margelo/MMKV) (`io.github.zhongwuzw:mmkv`) which re-adds armeabi-v7a (32-bit ARM) support, so you have full ABI coverage including 32-bit devices when using `react-native-mmkv`.
754
860
  </details>
755
861
 
756
862
  <details>
@@ -36,6 +36,8 @@ android {
36
36
  versionName '1.0'
37
37
 
38
38
  // Support for 16KB memory page sizes (Android 15+)
39
+ // Note: MMKV 2.x dropped armeabi-v7a support. If your app targets armeabi-v7a,
40
+ // downgrade to MMKV 1.x in your app's build.gradle (see README for details).
39
41
  ndk {
40
42
  abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
41
43
  }
@@ -78,8 +80,8 @@ dependencies {
78
80
  // MMKV dependency for persistent download state storage
79
81
  // Uses compileOnly to avoid duplicate class errors when app also uses react-native-mmkv
80
82
  // The app must provide MMKV dependency (either directly or via react-native-mmkv)
81
- // MMKV 2.0.0+ is required for 16KB memory page size support (Android 15+)
82
- compileOnly 'com.tencent:mmkv-shared:2.0.0'
83
+ // MMKV 1.3.14+ supports both armeabi-v7a (32-bit) and 16KB page sizes (Android 15+)
84
+ compileOnly 'com.tencent:mmkv-shared:1.3.16'
83
85
 
84
86
  implementation 'com.google.code.gson:gson:2.12.1'
85
87
  }
@@ -111,8 +111,18 @@ class Downloader(private val context: Context, private val storageManager: com.e
111
111
 
112
112
  /**
113
113
  * Set the download listener for resumable downloads.
114
+ * Also sets the UIDT listener on Android 14+ so that ongoing UIDT jobs
115
+ * (e.g. ones that survived app termination) can forward events to JS after
116
+ * the app is reopened.
114
117
  */
115
118
  fun setResumableDownloadListener(listener: ResumableDownloader.DownloadListener) {
119
+ // For UIDT jobs (Android 14+), set the listener directly on the registry.
120
+ // This reconnects event forwarding after app restart when UIDT jobs are
121
+ // already running but UIDTJobRegistry.downloadListener was null in the
122
+ // fresh process.
123
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
124
+ UIDTDownloadJobService.downloadListener = listener
125
+ }
116
126
  executeWhenServiceReady {
117
127
  downloadService?.setDownloadListener(listener)
118
128
  }