@kesha-antonov/react-native-background-downloader 4.4.5 → 4.5.3

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/README.md CHANGED
@@ -12,9 +12,13 @@
12
12
  <img src="https://img.shields.io/badge/New%20Architecture-supported-green.svg" alt="New Architecture">
13
13
  </p>
14
14
 
15
- # React Native Background Downloader
15
+ <h1 align="center">React Native Background Downloader</h1>
16
16
 
17
- Download and upload large files on iOS & Android — even when your app is in the background or terminated.
17
+ <p align="center">
18
+ Download and upload large files on iOS & Android — even when your app is in the background or terminated.
19
+ </p>
20
+
21
+ ---
18
22
 
19
23
  ## ✨ Features
20
24
 
@@ -28,7 +32,7 @@ Download and upload large files on iOS & Android — even when your app is in th
28
32
  - ⚡ **New Architecture** - Full TurboModules support for React Native
29
33
  - 📝 **TypeScript** - Complete TypeScript definitions included
30
34
 
31
- ## Why?
35
+ ## 💡 Why?
32
36
 
33
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.
34
38
 
@@ -40,35 +44,34 @@ Download and upload large files on iOS & Android — even when your app is in th
40
44
 
41
45
  **This Library:** `@kesha-antonov/react-native-background-downloader` wraps these native APIs in a simple, unified JavaScript interface. Start a download, close your app, reopen it hours later, and seamlessly reconnect to your ongoing downloads with a single function call.
42
46
 
43
- ## Table of Contents
44
-
45
- - [React Native Background Downloader](#react-native-background-downloader)
46
- - [ Features](#-features)
47
- - [Why?](#why)
48
- - [Table of Contents](#table-of-contents)
49
- - [Requirements](#requirements)
50
- - [Installation](#installation)
51
- - [Expo Projects](#expo-projects)
52
- - [Bare React Native Projects](#bare-react-native-projects)
53
- - [Usage](#usage)
54
- - [Downloading a file](#downloading-a-file)
55
- - [Re-Attaching to background tasks](#re-attaching-to-background-tasks)
56
- - [Advanced Configuration](#advanced-configuration)
57
- - [Max Parallel Downloads (iOS only)](#max-parallel-downloads-ios-only)
58
- - [Cellular/WiFi Restrictions](#cellularwifi-restrictions)
59
- - [API](#api)
60
- - [Quick Reference](#quick-reference)
61
- - [Platform Notes](#platform-notes)
62
- - [Troubleshooting](#troubleshooting)
63
- - [Example App](#example-app)
64
- - [Use Cases](#use-cases)
65
- - [Migration Guide](#migration-guide)
66
- - [Contributing](#contributing)
67
- - [Development Setup](#development-setup)
68
- - [Authors](#authors)
69
- - [License](#license)
70
-
71
- ## Requirements
47
+ ## 📖 Table of Contents
48
+
49
+ - [ Features](#-features)
50
+ - [💡 Why?](#-why)
51
+ - [📖 Table of Contents](#-table-of-contents)
52
+ - [📋 Requirements](#-requirements)
53
+ - [📦 Installation](#-installation)
54
+ - [Expo Projects](#expo-projects)
55
+ - [Bare React Native Projects](#bare-react-native-projects)
56
+ - [🚀 Usage](#-usage)
57
+ - [Downloading a file](#downloading-a-file)
58
+ - [Re-Attaching to background tasks](#re-attaching-to-background-tasks)
59
+ - [⚙️ Advanced Configuration](#️-advanced-configuration)
60
+ - [Max Parallel Downloads (iOS only)](#max-parallel-downloads-ios-only)
61
+ - [Cellular/WiFi Restrictions](#cellularwifi-restrictions)
62
+ - [📚 API](#-api)
63
+ - [Quick Reference](#quick-reference)
64
+ - [📱 Platform Notes](#-platform-notes)
65
+ - [ Troubleshooting](#-troubleshooting)
66
+ - [🧪 Example App](#-example-app)
67
+ - [💡 Use Cases](#-use-cases)
68
+ - [🔄 Migration Guide](#-migration-guide)
69
+ - [🤝 Contributing](#-contributing)
70
+ - [Development Setup](#development-setup)
71
+ - [👥 Authors](#-authors)
72
+ - [📄 License](#-license)
73
+
74
+ ## 📋 Requirements
72
75
 
73
76
  | Requirement | Version |
74
77
  |-------------|--------|
@@ -79,7 +82,7 @@ Download and upload large files on iOS & Android — even when your app is in th
79
82
 
80
83
  > **Note:** For older React Native versions (0.57.0 - 0.69.x), use version 2.x of this library.
81
84
 
82
- ## Installation
85
+ ## 📦 Installation
83
86
 
84
87
  ### Expo Projects
85
88
 
@@ -142,16 +145,24 @@ The plugin automatically handles:
142
145
 
143
146
  **Step 1:** Install the package
144
147
 
148
+ **yarn:**
145
149
  ```bash
146
- # Using yarn
147
150
  yarn add @kesha-antonov/react-native-background-downloader
151
+ ```
148
152
 
149
- # Using npm
153
+ **npm:**
154
+ ```bash
150
155
  npm install @kesha-antonov/react-native-background-downloader
151
156
  ```
152
157
 
153
158
  **Step 2:** Install iOS pods
154
159
 
160
+ **yarn:**
161
+ ```bash
162
+ cd ios && pod install && cd ..
163
+ ```
164
+
165
+ **npm:**
155
166
  ```bash
156
167
  cd ios && pod install && cd ..
157
168
  ```
@@ -209,7 +220,7 @@ dependencies {
209
220
 
210
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.
211
222
 
212
- ## Usage
223
+ ## 🚀 Usage
213
224
 
214
225
  ### Downloading a file
215
226
 
@@ -355,7 +366,45 @@ await task.stop()
355
366
 
356
367
  </details>
357
368
 
358
- ## Advanced Configuration
369
+ <details>
370
+ <summary><strong>Updating headers on paused downloads</strong></summary>
371
+
372
+ If your download uses authentication tokens that expire, you can update the headers of a paused download before resuming it. This is useful when auth tokens refresh while a download is paused:
373
+
374
+ ```javascript
375
+ import { getExistingDownloadTasks } from '@kesha-antonov/react-native-background-downloader'
376
+
377
+ // Get paused downloads
378
+ const tasks = await getExistingDownloadTasks()
379
+
380
+ for (const task of tasks) {
381
+ if (task.state === 'PAUSED') {
382
+ // Update headers with new auth token before resuming
383
+ await task.setDownloadParams({
384
+ ...task.downloadParams,
385
+ headers: {
386
+ ...task.downloadParams?.headers,
387
+ Authorization: 'Bearer new-refreshed-token'
388
+ }
389
+ })
390
+
391
+ // Now resume with the updated headers
392
+ await task.resume()
393
+ }
394
+ }
395
+ ```
396
+
397
+ **Notes:**
398
+ - `setDownloadParams()` is async and returns `true` if native headers were updated
399
+ - Headers are only updated in the native layer when the task is in `PAUSED` state
400
+ - On iOS, the download will resume using HTTP Range headers with the new headers
401
+ - On Android, both in-memory and persisted paused state are updated
402
+
403
+ > **Use case:** User pauses a large download, closes the app, and returns hours or days later. By then, the auth token has expired. This feature allows refreshing the token and updating headers before resuming, without restarting the download from scratch.
404
+
405
+ </details>
406
+
407
+ ## ⚙️ Advanced Configuration
359
408
 
360
409
  <details>
361
410
  <summary><strong>Using custom headers</strong></summary>
@@ -551,7 +600,191 @@ task.start()
551
600
 
552
601
  </details>
553
602
 
554
- ## API
603
+ <details>
604
+ <summary><strong>Notification Configuration (Android)</strong></summary>
605
+
606
+ On Android 14+ (API 34), downloads use User-Initiated Data Transfer (UIDT) jobs which **require** notifications. Due to Android system requirements, notifications cannot be completely disabled when using UIDT jobs. However, you can control their visibility:
607
+
608
+ - When `showNotificationsEnabled: true` - Full notifications with progress, title, and custom texts
609
+ - When `showNotificationsEnabled: false` (default) - Minimal silent notifications with lowest priority that are barely noticeable
610
+
611
+ **Basic configuration:**
612
+
613
+ ```javascript
614
+ import { setConfig } from '@kesha-antonov/react-native-background-downloader'
615
+
616
+ // Enable notifications and notification grouping with custom texts
617
+ setConfig({
618
+ showNotificationsEnabled: true, // Show full notifications (default: false - minimal silent notifications)
619
+ notificationsGrouping: {
620
+ enabled: true, // Enable grouping (default: false)
621
+ texts: {
622
+ downloadTitle: 'Download',
623
+ downloadStarting: 'Starting download...',
624
+ downloadProgress: 'Downloading... {progress}%',
625
+ downloadPaused: 'Paused',
626
+ downloadFinished: 'Download complete',
627
+ groupTitle: 'Downloads',
628
+ groupText: '{count} downloads in progress',
629
+ },
630
+ },
631
+ })
632
+
633
+ // Use minimal silent notifications (default behavior)
634
+ setConfig({
635
+ showNotificationsEnabled: false, // Minimal silent notifications (required by UIDT but barely visible)
636
+ })
637
+ ```
638
+
639
+ **Notification grouping modes:**
640
+
641
+ When downloading many files (e.g., thousands of photos), you can use the `mode` option to control how notifications are displayed:
642
+
643
+ | Mode | Description |
644
+ |------|-------------|
645
+ | `'individual'` | Default. Shows all individual notifications grouped together with a summary |
646
+ | `'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 |
647
+
648
+ ```javascript
649
+ import { setConfig } from '@kesha-antonov/react-native-background-downloader'
650
+
651
+ // For bulk downloads (e.g., syncing thousands of photos)
652
+ // Use 'summaryOnly' mode to show only ONE notification with aggregate progress
653
+ setConfig({
654
+ showNotificationsEnabled: true,
655
+ notificationsGrouping: {
656
+ enabled: true,
657
+ mode: 'summaryOnly', // Only show summary notification with progress bar
658
+ texts: {
659
+ groupTitle: 'Syncing Photos',
660
+ groupText: '{count} files downloading',
661
+ },
662
+ },
663
+ })
664
+ ```
665
+
666
+ **Example: Batch downloading multiple files with grouped notifications:**
667
+
668
+ ```javascript
669
+ import { setConfig, createDownloadTask, directories } from '@kesha-antonov/react-native-background-downloader'
670
+
671
+ // Configure for bulk downloads with single summary notification
672
+ setConfig({
673
+ showNotificationsEnabled: true,
674
+ notificationsGrouping: {
675
+ enabled: true,
676
+ mode: 'summaryOnly',
677
+ texts: {
678
+ groupTitle: 'Photo Sync',
679
+ groupText: '{count} photos downloading',
680
+ },
681
+ },
682
+ })
683
+
684
+ // Download multiple files - they all share ONE notification with aggregate progress
685
+ const photos = [
686
+ { id: 'photo-1', url: 'https://example.com/photo1.jpg' },
687
+ { id: 'photo-2', url: 'https://example.com/photo2.jpg' },
688
+ { id: 'photo-3', url: 'https://example.com/photo3.jpg' },
689
+ // ... potentially thousands of files
690
+ ]
691
+
692
+ const GROUP_ID = 'photo-sync-batch'
693
+
694
+ for (const photo of photos) {
695
+ const task = createDownloadTask({
696
+ id: photo.id,
697
+ url: photo.url,
698
+ destination: `${directories.documents}/${photo.id}.jpg`,
699
+ metadata: {
700
+ groupId: GROUP_ID, // Required for grouping
701
+ groupName: 'Photo Sync', // Displayed in notification title
702
+ },
703
+ })
704
+ .progress(({ bytesDownloaded, bytesTotal }) => {
705
+ // Progress tracked per file, notification shows aggregate progress
706
+ })
707
+ .done(() => {
708
+ console.log(`Downloaded ${photo.id}`)
709
+ })
710
+ .error(({ error }) => {
711
+ console.error(`Failed ${photo.id}:`, error)
712
+ })
713
+
714
+ task.start()
715
+ }
716
+
717
+ // User sees: ONE notification showing "45% - 3 files" with progress bar
718
+ // instead of 3 separate notifications cluttering the notification shade
719
+ // Notification automatically disappears when all downloads complete
720
+ ```
721
+
722
+ **Grouping downloads by category:**
723
+
724
+ 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`:
725
+
726
+ ```javascript
727
+ import { createDownloadTask, directories } from '@kesha-antonov/react-native-background-downloader'
728
+
729
+ // Download album songs - they will be grouped under one notification
730
+ const task = createDownloadTask({
731
+ id: 'song-1',
732
+ url: 'https://example.com/albums/summer-hits/track01.mp3',
733
+ destination: `${directories.documents}/track01.mp3`,
734
+ metadata: {
735
+ groupId: 'album-summer-hits', // Unique identifier for the group
736
+ groupName: 'Summer Hits 2024', // Display name in notification title
737
+ },
738
+ })
739
+
740
+ task.start()
741
+ ```
742
+
743
+ **Notification behavior during pause/resume:**
744
+
745
+ When a download is paused on Android 14+:
746
+ - The background UIDT job is cancelled (to prevent downloads continuing in background)
747
+ - A detached "Paused" notification remains visible showing current progress
748
+ - When resumed, a new UIDT job is created and the notification switches to "Downloading" state
749
+ - When stopped, the notification is removed
750
+ - **When app is closed, all download notifications are automatically removed**
751
+
752
+ This ensures users always see the download status without unexpected background activity.
753
+
754
+ **Configuration options:**
755
+
756
+ | Option | Type | Default | Description |
757
+ |--------|------|---------|-------------|
758
+ | `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. |
759
+ | `notificationsGrouping.enabled` | boolean | `false` | Enable notification grouping |
760
+ | `notificationsGrouping.mode` | `'individual'` \| `'summaryOnly'` | `'individual'` | Notification display mode. Use `'summaryOnly'` for bulk downloads to show only ONE notification with real-time aggregate progress |
761
+ | `notificationsGrouping.texts` | object | See below | Customizable notification texts |
762
+
763
+ **Notification texts (`notificationsGrouping.texts`):**
764
+
765
+ | Key | Default | Placeholders | Description |
766
+ |-----|---------|--------------|-------------|
767
+ | `downloadTitle` | `'Download'` | — | Title for individual download notifications |
768
+ | `downloadStarting` | `'Starting download...'` | — | Text when download is starting |
769
+ | `downloadProgress` | `'Downloading... {progress}%'` | `{progress}` | Progress text with current percentage (0-100) |
770
+ | `downloadPaused` | `'Paused'` | — | Text when download is paused |
771
+ | `downloadFinished` | `'Download complete'` | — | Text when download is finished |
772
+ | `groupTitle` | `'Downloads'` | — | Title for group summary notification |
773
+ | `groupText` | `'{count} download(s) in progress'` | `{count}` | Group summary text with active downloads count |
774
+
775
+ **Notes:**
776
+ - Notifications cannot be completely disabled on Android 14+ due to UIDT requirements
777
+ - When `showNotificationsEnabled: false`, notifications are created with minimal visibility (lowest priority, empty content)
778
+ - Paused downloads show a non-ongoing notification (can be swiped away by user)
779
+ - Active downloads show an ongoing notification (cannot be swiped away)
780
+ - This feature only affects Android 14+ (API 34) where UIDT jobs are used
781
+ - On older Android versions, the standard DownloadManager notifications are shown
782
+ - iOS uses system download notifications and doesn't support custom grouping
783
+ - **Use `mode: 'summaryOnly'` when downloading many files** to prevent notification spam - shows ONE notification with aggregate progress bar that updates in real-time
784
+
785
+ </details>
786
+
787
+ ## 📚 API
555
788
 
556
789
  For complete API documentation, see the **[API Reference](./docs/API.md)**.
557
790
 
@@ -579,7 +812,7 @@ import {
579
812
  | `completeHandler(jobId)` | Signal download completion to OS |
580
813
  | `directories.documents` | Path to app's documents directory |
581
814
 
582
- ## Platform Notes
815
+ ## 📱 Platform Notes
583
816
 
584
817
  For detailed platform-specific information, see **[Platform Notes](./docs/PLATFORM_NOTES.md)**.
585
818
 
@@ -588,7 +821,7 @@ Key points:
588
821
  - **Android**: Uses `DownloadManager` + Foreground Services + MMKV
589
822
  - **Pause/Resume**: Works on both platforms (Android requires server Range header support)
590
823
 
591
- ## Troubleshooting
824
+ ## Troubleshooting
592
825
 
593
826
  <details>
594
827
  <summary><strong>Download stuck in "pending" state (Android)</strong></summary>
@@ -629,7 +862,7 @@ See the [Google Play Console Declaration](#google-play-console-declaration) sect
629
862
  Add the Proguard rules mentioned in the [Proguard Rules](#proguard-rules) section.
630
863
  </details>
631
864
 
632
- ## Example App
865
+ ## 🧪 Example App
633
866
 
634
867
  The repository includes a full example app demonstrating all features:
635
868
 
@@ -652,7 +885,7 @@ The example app shows:
652
885
  - Re-attaching to background tasks
653
886
  - File management
654
887
 
655
- ## Use Cases
888
+ ## 💡 Use Cases
656
889
 
657
890
  This library is perfect for apps that need reliable file transfers:
658
891
 
@@ -663,7 +896,7 @@ This library is perfect for apps that need reliable file transfers:
663
896
  - 🎮 **Games** - Download game assets and updates
664
897
  - 📱 **Enterprise Apps** - Sync large documents and media
665
898
 
666
- ## Migration Guide
899
+ ## 🔄 Migration Guide
667
900
 
668
901
  Upgrading from an older version? Check the [Migration Guide](./MIGRATION.md) for detailed instructions:
669
902
 
@@ -674,7 +907,7 @@ Upgrading from an older version? Check the [Migration Guide](./MIGRATION.md) for
674
907
 
675
908
  See the [Changelog](./CHANGELOG.md) for a complete list of changes in each version.
676
909
 
677
- ## Contributing
910
+ ## 🤝 Contributing
678
911
 
679
912
  Contributions are welcome! Please feel free to submit a Pull Request.
680
913
 
@@ -708,12 +941,14 @@ cd example && yarn install
708
941
  yarn ios # or yarn android
709
942
  ```
710
943
 
711
- ## Authors
944
+ ## 👥 Authors
712
945
 
713
946
  Maintained by [Kesha Antonov](https://github.com/kesha-antonov)
714
947
 
715
948
  Based on [react-native-background-downloader](https://github.com/ekolabs/react-native-background-downloader) by [Elad Gil](https://github.com/ptelad) (unmaintained since 2019)
716
949
 
717
- ## License
950
+ > Please note that this project is maintained in free time. If you find it helpful, please consider [becoming a sponsor](https://github.com/sponsors/kesha-antonov).
951
+
952
+ ## 📄 License
718
953
 
719
954
  [Apache 2.0](./LICENSE)
@@ -248,6 +248,19 @@ class Downloader(private val context: Context, private val storageManager: com.e
248
248
  RNBackgroundDownloaderModuleImpl.logD(TAG, "Saved paused state for resumable download $configId at $bytesDownloaded/$bytesTotal bytes")
249
249
  }
250
250
 
251
+ /**
252
+ * Update headers for a paused download.
253
+ * This allows changing auth tokens before resuming.
254
+ */
255
+ fun updatePausedDownloadHeaders(configId: String, headers: Map<String, String>) {
256
+ val pausedInfo = pausedDownloads[configId] ?: return
257
+
258
+ pausedDownloads[configId] = pausedInfo.copy(headers = headers)
259
+ savePausedDownloads()
260
+
261
+ RNBackgroundDownloaderModuleImpl.logD(TAG, "Updated headers for paused download $configId")
262
+ }
263
+
251
264
  /**
252
265
  * Resume a paused download using the background service with HTTP Range headers.
253
266
  */
@@ -269,7 +282,8 @@ class Downloader(private val context: Context, private val storageManager: com.e
269
282
  pausedInfo.headers,
270
283
  pausedInfo.bytesDownloaded,
271
284
  pausedInfo.bytesTotal,
272
- listener
285
+ listener,
286
+ pausedInfo.metadata
273
287
  )
274
288
 
275
289
  RNBackgroundDownloaderModuleImpl.logD(TAG, "Resuming download $configId from ${pausedInfo.bytesDownloaded} bytes via service")
@@ -293,7 +307,8 @@ class Downloader(private val context: Context, private val storageManager: com.e
293
307
  headers: Map<String, String>,
294
308
  startByte: Long,
295
309
  totalBytes: Long,
296
- listener: ResumableDownloader.DownloadListener
310
+ listener: ResumableDownloader.DownloadListener,
311
+ metadata: String = "{}"
297
312
  ) {
298
313
  // On Android 14+, use UIDT jobs for better background execution
299
314
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
@@ -308,7 +323,8 @@ class Downloader(private val context: Context, private val storageManager: com.e
308
323
  destination = destination,
309
324
  headers = headers,
310
325
  startByte = startByte,
311
- totalBytes = totalBytes
326
+ totalBytes = totalBytes,
327
+ metadata = metadata
312
328
  )
313
329
 
314
330
  if (scheduled) {
@@ -351,7 +367,8 @@ class Downloader(private val context: Context, private val storageManager: com.e
351
367
  url: String,
352
368
  destination: String,
353
369
  headers: Map<String, String>,
354
- listener: ResumableDownloader.DownloadListener
370
+ listener: ResumableDownloader.DownloadListener,
371
+ metadata: String = "{}"
355
372
  ) {
356
373
  startDownloadService(
357
374
  configId,
@@ -360,7 +377,8 @@ class Downloader(private val context: Context, private val storageManager: com.e
360
377
  headers,
361
378
  0L, // Start from beginning
362
379
  -1L, // Total bytes unknown
363
- listener
380
+ listener,
381
+ metadata
364
382
  )
365
383
  RNBackgroundDownloaderModuleImpl.logD(TAG, "Started ResumableDownloader for $configId (DownloadManager fallback)")
366
384
  }
@@ -372,7 +390,7 @@ class Downloader(private val context: Context, private val storageManager: com.e
372
390
  // Check UIDT jobs on Android 14+
373
391
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
374
392
  if (UIDTDownloadJobService.isActiveJob(configId)) {
375
- return UIDTDownloadJobService.pauseJob(configId)
393
+ return UIDTDownloadJobService.pauseJob(context, configId)
376
394
  }
377
395
  }
378
396
  return downloadService?.pauseDownload(configId) ?: false
@@ -385,7 +403,7 @@ class Downloader(private val context: Context, private val storageManager: com.e
385
403
  // Check UIDT jobs on Android 14+
386
404
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
387
405
  if (UIDTDownloadJobService.isActiveJob(configId)) {
388
- return UIDTDownloadJobService.resumeJob(configId, listener)
406
+ return UIDTDownloadJobService.resumeJob(context, configId, listener)
389
407
  }
390
408
  }
391
409
  executeWhenServiceReady {
@@ -7,6 +7,5 @@ data class RNBGDTaskConfig(
7
7
  var url: String,
8
8
  var destination: String,
9
9
  var metadata: String = "{}",
10
- var notificationTitle: String?,
11
10
  var reportedBegin: Boolean = false
12
11
  ) : Serializable
@@ -14,7 +14,6 @@ data class RNBGDUploadTaskConfig(
14
14
  val fieldName: String? = null,
15
15
  val mimeType: String? = null,
16
16
  val parameters: Map<String, String>? = null,
17
- val notificationTitle: String? = null,
18
17
  var reportedBegin: Boolean = false,
19
18
  var bytesUploaded: Long = 0,
20
19
  var bytesTotal: Long = 0,