Skip to content

Commit

Permalink
Version 1.6.0
Browse files Browse the repository at this point in the history
Added field `requiresWiFi` to the `BackgroundDownloadTask`. If set to true, the task won't start downloading unless a WiFi network is available. By default `requiresWiFi` is false, and downloads will use the cellular (or metered) network if WiFi is not available, which may incur cost.
  • Loading branch information
781flyingdutchman committed Jan 23, 2023
1 parent 6a7e230 commit 4504ce8
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 22 deletions.
22 changes: 17 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
## 1.6.0

Added option to set `requiresWiFi` on the `BackgroundDownloadTask`, which ensures the task won't
start downloading unless a WiFi network is available. By default `requiresWiFi` is false, and
downloads will use the cellular (or metered) network if WiFi is not available, which may incur
cost.

## 1.5.0

Added `allTasks` method to get a list of running tasks. Use `allTaskIds` to get a list of taskIds only.
Added `allTasks` method to get a list of running tasks. Use `allTaskIds` to get a list of taskIds
only.

## 1.4.2

Added note to README referring to an issue (and [fix](https://github.com/firebase/flutterfire/issues/9689#issuecomment-1304491789)) where the firebase plugin interferes with the downloader
Added note to README referring to an issue (
and [fix](https://github.com/firebase/flutterfire/issues/9689#issuecomment-1304491789)) where the
firebase plugin interferes with the downloader

## 1.4.1

Expand All @@ -20,17 +30,19 @@ Added option to use an event listener instead of (or in addition to) callbacks

## 1.2.0

Added FileDownloader.download as a convenience method for simple downloads. This method's Future completes only after the download has completed or failed, and can be used for simple downloads where status and progress checking is not required.
Added FileDownloader.download as a convenience method for simple downloads. This method's Future
completes only after the download has completed or failed, and can be used for simple downloads
where status and progress checking is not required.

## 1.1.0

Added headers and metaData fields to the BackgroundDownloadTask. Headers will be added to the request, and metaData is ignored but may be helpful to the user
Added headers and metaData fields to the BackgroundDownloadTask. Headers will be added to the
request, and metaData is ignored but may be helpful to the user

## 1.0.2

Replaced Ktor client with a basic Kotlin implementation


## 1.0.0

Initial release
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ The callback will be called upon completion of each task (whether successful or

Optionally, `headers` can be added to the `BackgroundDownloadTask`, which will be added to the HTTP request. This may be useful for authentication, for example.

### Requiring WiFi

If the `requiresWiFi` field of a `BackgroundDownloadTask` is set to true, the task won't start downloading unless a WiFi network is available. By default `requiresWiFi` is false, and downloads will use the cellular (or metered) network if WiFi is not available, which may incur cost.


### Metadata

Also optionally, `metaData` can be added to the `BackgroundDownloadTask` (a `String`). Metadata is ignored by the downloader but may be helpful when receiving an update about the task.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ class BackgroundDownloaderPlugin : FlutterPlugin, MethodCallHandler {
}

override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.bbflight.background_downloader")
backgroundChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.bbflight.background_downloader.background")
channel = MethodChannel(flutterPluginBinding.binaryMessenger,
"com.bbflight.background_downloader")
backgroundChannel = MethodChannel(flutterPluginBinding.binaryMessenger,
"com.bbflight.background_downloader.background")
channel?.setMethodCallHandler(this)
workManager = WorkManager.getInstance(flutterPluginBinding.applicationContext)
prefs = PreferenceManager.getDefaultSharedPreferences(flutterPluginBinding.applicationContext)
prefs = PreferenceManager.getDefaultSharedPreferences(
flutterPluginBinding.applicationContext)
val allWorkInfos = workManager.getWorkInfosByTag(TAG).get()
if (allWorkInfos.isEmpty()) {
// remove persistent storage if no jobs found at all
Expand Down Expand Up @@ -70,25 +73,36 @@ class BackgroundDownloaderPlugin : FlutterPlugin, MethodCallHandler {
private fun methodEnqueue(call: MethodCall, result: Result) {
val args = call.arguments as List<*>
val downloadTaskJsonMapString = args[0] as String
val backgroundDownloadTask = BackgroundDownloadTask(gson.fromJson(downloadTaskJsonMapString, mapType))
val backgroundDownloadTask =
BackgroundDownloadTask(gson.fromJson(downloadTaskJsonMapString, mapType))
Log.v(TAG, "Starting task with id ${backgroundDownloadTask.taskId}")
val data = Data.Builder().putString(DownloadWorker.keyDownloadTask, downloadTaskJsonMapString).build()
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
val request = OneTimeWorkRequestBuilder<DownloadWorker>().setInputData(data).setConstraints(constraints).addTag(TAG).addTag("taskId=${backgroundDownloadTask.taskId}").addTag("group=${backgroundDownloadTask.group}").build()
val data =
Data.Builder().putString(DownloadWorker.keyDownloadTask, downloadTaskJsonMapString)
.build()
val constraints = Constraints.Builder().setRequiredNetworkType(
if (backgroundDownloadTask.requiresWiFi) NetworkType.UNMETERED else NetworkType.CONNECTED)
.build()
val request = OneTimeWorkRequestBuilder<DownloadWorker>().setInputData(data)
.setConstraints(constraints).addTag(TAG)
.addTag("taskId=${backgroundDownloadTask.taskId}")
.addTag("group=${backgroundDownloadTask.group}").build()
val operation = workManager.enqueue(request)
try {
operation.result.get()
DownloadWorker.processStatusUpdate(backgroundDownloadTask, DownloadTaskStatus.running)
} catch (e: Throwable) {
Log.w(TAG, "Unable to start background request for taskId ${backgroundDownloadTask.taskId} in operation: $operation")
Log.w(TAG,
"Unable to start background request for taskId ${backgroundDownloadTask.taskId} in operation: $operation")
result.success(false)
return
}
// store Task in persistent storage, as Json representation keyed by taskId
prefsLock.write {
val jsonString = prefs.getString(keyTasksMap, "{}")
val backgroundDownloadTaskMap = gson.fromJson<Map<String, Any>>(jsonString, mapType).toMutableMap()
backgroundDownloadTaskMap[backgroundDownloadTask.taskId] = gson.toJson(backgroundDownloadTask.toJsonMap())
val backgroundDownloadTaskMap =
gson.fromJson<Map<String, Any>>(jsonString, mapType).toMutableMap()
backgroundDownloadTaskMap[backgroundDownloadTask.taskId] =
gson.toJson(backgroundDownloadTask.toJsonMap())
val editor = prefs.edit()
editor.putString(keyTasksMap, gson.toJson(backgroundDownloadTaskMap))
editor.apply()
Expand All @@ -103,7 +117,8 @@ class BackgroundDownloaderPlugin : FlutterPlugin, MethodCallHandler {
private fun methodReset(call: MethodCall, result: Result) {
val group = call.arguments as String
var counter = 0
val workInfos = workManager.getWorkInfosByTag(TAG).get().filter { !it.state.isFinished && it.tags.contains("group=$group") }
val workInfos = workManager.getWorkInfosByTag(TAG).get()
.filter { !it.state.isFinished && it.tags.contains("group=$group") }
for (workInfo in workInfos) {
workManager.cancelWorkById(workInfo.id)
counter++
Expand All @@ -115,7 +130,8 @@ class BackgroundDownloaderPlugin : FlutterPlugin, MethodCallHandler {
/** Returns a list of taskIds for all tasks in progress */
private fun methodAllTaskIds(call: MethodCall, result: Result) {
val group = call.arguments as String
val workInfos = workManager.getWorkInfosByTag(TAG).get().filter { !it.state.isFinished && it.tags.contains("group=$group") }
val workInfos = workManager.getWorkInfosByTag(TAG).get()
.filter { !it.state.isFinished && it.tags.contains("group=$group") }
val taskIds = mutableListOf<String>()
for (workInfo in workInfos) {
val tags = workInfo.tags.filter { it.contains("taskId=") }
Expand All @@ -130,7 +146,8 @@ class BackgroundDownloaderPlugin : FlutterPlugin, MethodCallHandler {
/** Returns a list of tasks for all tasks in progress, as a list of JSON strings */
private fun methodAllTasks(call: MethodCall, result: Result) {
val group = call.arguments as String
val workInfos = workManager.getWorkInfosByTag(TAG).get().filter { !it.state.isFinished && it.tags.contains("group=$group") }
val workInfos = workManager.getWorkInfosByTag(TAG).get()
.filter { !it.state.isFinished && it.tags.contains("group=$group") }
val tasksAsListOfJsonStrings = mutableListOf<String>()
prefsLock.read {
val jsonString = prefs.getString(keyTasksMap, "{}")
Expand Down Expand Up @@ -172,7 +189,8 @@ class BackgroundDownloaderPlugin : FlutterPlugin, MethodCallHandler {
Log.v(TAG, "Returning task for taskId $taskId")
prefsLock.read {
val jsonString = prefs.getString(keyTasksMap, "{}")
val backgroundDownloadTaskMap = gson.fromJson<Map<String, Any>>(jsonString, mapType).toMutableMap()
val backgroundDownloadTaskMap =
gson.fromJson<Map<String, Any>>(jsonString, mapType).toMutableMap()
result.success(backgroundDownloadTaskMap[taskId])
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class BackgroundDownloadTask(
val baseDirectory: BaseDirectory,
val group: String,
val progressUpdates: DownloadTaskProgressUpdates,
val requiresWiFi: Boolean,
val metaData: String
) {

Expand All @@ -70,6 +71,7 @@ class BackgroundDownloadTask(
group = jsonMap["group"] as String,
progressUpdates =
DownloadTaskProgressUpdates.values()[(jsonMap["progressUpdates"] as Double).toInt()],
requiresWiFi = jsonMap["requiresWiFi"] as Boolean,
metaData = jsonMap["metaData"] as String
)

Expand All @@ -84,6 +86,7 @@ class BackgroundDownloadTask(
"baseDirectory" to baseDirectory.ordinal, // stored as int
"group" to group,
"progressUpdates" to progressUpdates.ordinal,
"requiresWiFi" to requiresWiFi,
"metaData" to metaData
)
}
Expand Down
36 changes: 36 additions & 0 deletions example/integration_test/downloader_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,42 @@ void main() {
expect(lastDownloadStatus, equals(DownloadTaskStatus.complete));
print('Finished taskForId');
});

testWidgets('Task to and from Json', (widgetTester) async {
final complexTask = BackgroundDownloadTask(
taskId: 'uniqueId',
url: 'https://google.com',
filename: 'google.html',
headers: {'Auth': 'Test'},
directory: 'directory',
baseDirectory: BaseDirectory.temporary,
group: 'someGroup',
progressUpdates:
DownloadTaskProgressUpdates.statusChangeAndProgressUpdates,
requiresWiFi: true,
metaData: 'someMetaData');
FileDownloader.initialize(
group: complexTask.group,
downloadStatusCallback: downloadStatusCallback);
expect(await FileDownloader.taskForId(complexTask.taskId), isNull);
expect(await FileDownloader.enqueue(complexTask), isTrue);
final task = await FileDownloader.taskForId(complexTask.taskId);
expect(task, equals(complexTask));
if (task != null) {
expect(task.taskId, equals(complexTask.taskId));
expect(task.url, equals(complexTask.url));
expect(task.filename, equals(complexTask.filename));
expect(task.headers, equals(complexTask.headers));
expect(task.directory, equals(complexTask.directory));
expect(task.baseDirectory, equals(complexTask.baseDirectory));
expect(task.group, equals(complexTask.group));
expect(task.progressUpdates, equals(complexTask.progressUpdates));
expect(task.requiresWiFi, equals(complexTask.requiresWiFi));
expect(task.metaData, equals(complexTask.metaData));
}
await downloadStatusCallbackCompleter.future;
expect(lastDownloadStatus, equals(DownloadTaskStatus.complete));
});
});

group('Convenience downloads', () {
Expand Down
7 changes: 6 additions & 1 deletion ios/Classes/Downloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct BackgroundDownloadTask : Codable {
var baseDirectory: Int
var group: String
var progressUpdates: Int
var requiresWiFi: Bool
var metaData: String
}

Expand All @@ -28,14 +29,15 @@ func jsonMapFromTask(task: BackgroundDownloadTask) -> [String: Any] {
"baseDirectory": task.baseDirectory, // stored as Int
"group": task.group,
"progressUpdates": task.progressUpdates, // stored as Int
"requiresWiFi": task.requiresWiFi,
"metaData": task.metaData
]

}

/// Creates task from JsonMap
func taskFromJsonMap(map: [String: Any]) -> BackgroundDownloadTask {
return BackgroundDownloadTask(taskId: map["taskId"] as! String, url: map["url"] as! String, filename: map["filename"] as! String, headers: map["headers"] as! [String:String], directory: map["directory"] as! String, baseDirectory: map["baseDirectory"] as! Int, group: map["group"] as! String, progressUpdates: map["progressUpdates"] as! Int, metaData: map["metaData"] as! String)
return BackgroundDownloadTask(taskId: map["taskId"] as! String, url: map["url"] as! String, filename: map["filename"] as! String, headers: map["headers"] as! [String:String], directory: map["directory"] as! String, baseDirectory: map["baseDirectory"] as! Int, group: map["group"] as! String, progressUpdates: map["progressUpdates"] as! Int, requiresWiFi: map["requiresWiFi"] as! Bool, metaData: map["metaData"] as! String)
}

/// True if this task expects to provide progress updates
Expand Down Expand Up @@ -147,6 +149,9 @@ public class Downloader: NSObject, FlutterPlugin, FlutterApplicationLifeCycleDel
os_log("Starting task with id %@", log: log, type: .info, backgroundDownloadTask.taskId)
urlSession = urlSession ?? createUrlSession()
var request = URLRequest(url: URL(string: backgroundDownloadTask.url)!)
if backgroundDownloadTask.requiresWiFi {
request.allowsCellularAccess = false
}
for (key, value) in backgroundDownloadTask.headers {
request.setValue(value, forHTTPHeaderField: key)
}
Expand Down
10 changes: 9 additions & 1 deletion lib/src/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ class BackgroundDownloadTask {
/// Type of progress updates desired
final DownloadTaskProgressUpdates progressUpdates;

/// If true, will not download over cellular (metered) network
final bool requiresWiFi;

/// User-defined metadata
final String metaData;

Expand All @@ -121,6 +124,7 @@ class BackgroundDownloadTask {
this.baseDirectory = BaseDirectory.applicationDocuments,
this.group = 'default',
this.progressUpdates = DownloadTaskProgressUpdates.statusChange,
this.requiresWiFi = false,
this.metaData = ''})
: taskId = taskId ?? Random().nextInt(1 << 32).toString() {
if (filename.isEmpty) {
Expand All @@ -146,6 +150,7 @@ class BackgroundDownloadTask {
BaseDirectory? baseDirectory,
String? group,
DownloadTaskProgressUpdates? progressUpdates,
bool? requiresWiFi,
String? metaData}) =>
BackgroundDownloadTask(
taskId: taskId ?? this.taskId,
Expand All @@ -156,6 +161,7 @@ class BackgroundDownloadTask {
baseDirectory: baseDirectory ?? this.baseDirectory,
group: group ?? this.group,
progressUpdates: progressUpdates ?? this.progressUpdates,
requiresWiFi: requiresWiFi ?? this.requiresWiFi,
metaData: metaData ?? this.metaData);

/// Creates object from JsonMap
Expand All @@ -169,6 +175,7 @@ class BackgroundDownloadTask {
group = jsonMap['group'],
progressUpdates =
DownloadTaskProgressUpdates.values[jsonMap['progressUpdates']],
requiresWiFi = jsonMap['requiresWiFi'],
metaData = jsonMap['metaData'];

/// Creates JSON map of this object
Expand All @@ -180,7 +187,8 @@ class BackgroundDownloadTask {
'directory': directory,
'baseDirectory': baseDirectory.index, // stored as int
'group': group,
'progressUpdates': progressUpdates.index,
'progressUpdates': progressUpdates.index, // stored as int
'requiresWiFi': requiresWiFi,
'metaData': metaData
};

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: background_downloader
description: A background file downloader for iOS and Android. Define where to get your file from, where to store it, enqueue your task and monitor it
version: 1.5.0
version: 1.6.0
repository: https://github.com/781flyingdutchman/background_downloader

environment:
Expand Down

0 comments on commit 4504ce8

Please sign in to comment.