Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
e8f9d39
feat: Bugfix release 0.7.1 with multiple contributor fixes
ened Jun 24, 2025
6fbdff9
fix: Update Package.swift to remove archived flutter/engine dependency
ened Jun 24, 2025
e1b44b0
fix: Remove premature SPM implementation to avoid file duplication
ened Jun 24, 2025
fcb6452
feat: Migrate to federated plugin architecture for v0.8.0
ened Jun 24, 2025
6e39275
fix: Remove duplicated files and fix federated package issues
ened Jun 24, 2025
3621eb0
chore: Replace be.tramckrijte namespace with dev.fluttercommunity
ened Jun 24, 2025
6bfdf51
fix: Update GitHub Actions for federated architecture
ened Jun 24, 2025
6a7999b
fix: Restore Dart unit tests and fix generator issues
ened Jun 24, 2025
d32ab57
fix: Complete namespace migration and fix iOS module import
ened Jun 24, 2025
db90e79
style: Apply dart formatter to all packages
ened Jun 24, 2025
33614ba
fix: Remove ios_backup directory causing Swift lint failures
ened Jun 24, 2025
3ef530c
debug: Add detailed git diff output to format_dart workflow
ened Jun 24, 2025
ecaab1e
feat: Complete federated plugin architecture with comprehensive tests
ened Jun 24, 2025
844c27f
fix: Simplify format_dart CI check to properly detect formatting issues
ened Jun 24, 2025
0e8bbfc
fix: Add flutter pub get before dart format to resolve dependencies
ened Jun 24, 2025
fa90c25
feat: Update pubspec files for publishable packages
ened Jun 24, 2025
d35e4d4
fix: Add melos bootstrap to analysis workflow and fix example dependency
ened Jun 24, 2025
96f16d7
fix: Make initialDelaySeconds optional in iOS registerOneOffTask
ened Jun 24, 2025
b14ae1c
remove version in example/pubspec
ened Jun 24, 2025
1f3d7e5
fix dart not found
ened Jun 24, 2025
27e94ce
feat: Add LICENSE and README files for all published packages
ened Jun 24, 2025
6de804f
fix: Improve federated plugin documentation and melos CI setup
ened Jun 24, 2025
f26832c
fix: Resolve drive_android test failures and integration test logic e…
ened Jun 24, 2025
24714c7
fix: Replace melos bootstrap with manual dependency resolution in ana…
ened Jun 24, 2025
c38f5ea
fix: Replace manual melos setup with official melos-action
ened Jun 24, 2025
64999b6
debug: Add comprehensive debugging to analysis workflow
ened Jun 24, 2025
3f3e018
fix: Update analysis workflow for federated plugin architecture
ened Jun 25, 2025
fc18018
docs: Add CHANGELOG.md files to all federated plugin packages
ened Jun 25, 2025
994e80e
fix: only run pub publish analysis for now
ened Jun 25, 2025
20e6632
chore: Upgrade to Flutter 3.32 and flutter_lints 6.0.0
ened Jun 25, 2025
e4867b3
chore: Update Android emulator tests to use API level 35
ened Jun 25, 2025
846f8ac
fix: Fix Android test compilation issues
ened Jun 25, 2025
d4d838a
fix: do not double build
ened Jun 25, 2025
4b70405
fix: Correct native Android test command in CI
ened Jun 25, 2025
5df937e
chore: Remove test_pana folder
ened Jun 25, 2025
a8a8dd5
Revert "fix: do not double build"
ened Jun 25, 2025
7ef8194
fix: Handle missing isInDebugMode parameter in Android task registration
ened Jun 25, 2025
07ba57f
perf: Add Android emulator caching to improve CI performance
ened Jun 25, 2025
2d89760
try in one line
ened Jun 25, 2025
cd19b48
fix: Handle inputData Map properly and add comprehensive integration …
ened Jun 25, 2025
94be83b
refactor: Remove JSON conversion and use native Map transfer
ened Jun 25, 2025
5020f6a
fix: iOS compilation error and remove verbose flags
ened Jun 25, 2025
2f2b0a7
fix: Update enum values to camelCase for Dart conventions
ened Jun 25, 2025
662d4ba
fix: Resolve iOS build failure and integration test method names
ened Jun 25, 2025
0fa0f3b
style: Apply dart format to ensure consistent code style
ened Jun 25, 2025
7183db1
style: Apply ktlint formatting to Kotlin code
ened Jun 25, 2025
b371433
fix: Remove accidentally committed ktlint binary
ened Jun 25, 2025
17d4f22
fix: Add enum mapping for camelCase Dart values to Android enum names
ened Jun 25, 2025
1dfddf4
style: Apply ktlint formatting to when statement indentation
ened Jun 25, 2025
7d67007
fix: Separate expedited job test from regular constraint test
ened Jun 25, 2025
920c80e
docs: Update CHANGELOGs and READMEs for v0.8.0 release
ened Jun 25, 2025
cfa147f
fix: Improve input data handling and background channel initialization
ened Jun 27, 2025
0d09c0e
test: Add integration tests for data transfer and retry functionality
ened Jul 1, 2025
da8e9a4
fix: ios tests
ened Jul 1, 2025
fab7de4
refactor: Rename workmanager_ios to workmanager_apple
ened Jul 1, 2025
79f73a1
last renames
ened Jul 1, 2025
231a448
Fixed the analysis
ened Jul 1, 2025
2b19081
keep onResultSend method
ened Jul 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions workmanager/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# 0.7.1

* Android: Fix v2 embedding import in BackgroundWorker by @jogapps (from PR #595)
* Android: Fix documentation formatting and typo in BackgroundWorker by @jogapps (from PR #595)
* iOS: Fix swapped constraints bug for requiresNetworkConnectivity and requiresExternalPower by @thegriffen (from PR #562)
* iOS: Add Privacy Manifest for App Store compliance by @navaronbracke (from PR #555)
* iOS: Replace print statements with proper os_log for better logging
* iOS: printScheduledTasks now returns String instead of void by @yarith28 (from PR #585)
* iOS: Add Swift Package Manager support for future Flutter compatibility

# 0.7.0

* **BREAKING**: Minimum Dart SDK bumped to 3.2.0
Expand Down
2 changes: 1 addition & 1 deletion workmanager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

Flutter WorkManager is a wrapper around [Android's WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager), [iOS' performFetchWithCompletionHandler](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623125-application) and [iOS BGAppRefreshTask](https://developer.apple.com/documentation/backgroundtasks/bgapprefreshtask), effectively enabling headless execution of Dart code in the background.

For iOS users, please watch this video on a general introduction to background processing: https://developer.apple.com/videos/play/wwdc2019/707. All of the constraints discussed in the video also apply to this plugin.
For iOS users, please watch this video on a general introduction to background processing: https://developer.apple.com/videos/play/wwdc2019/707. All of the constraints discussed in the video also apply to this plugin.

This is especially useful to run periodic tasks, such as fetching remote data on a regular basis.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import com.google.common.util.concurrent.ListenableFuture
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.embedding.engine.loader.FlutterLoader
import io.flutter.embedding.engine.loader.FlutterCallbackInformation
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.view.FlutterCallbackInformation
import java.util.Random

/***
* A simple worker that will post your input back to your Flutter application.
/**
* A simple worker that posts your input back to your Flutter application.
*
* It will block the background thread until a value of either true or false is received back from Flutter code.
*/
Expand Down Expand Up @@ -165,8 +165,8 @@ class BackgroundWorker(
}

override fun success(receivedResult: Any?) {
val wasSuccessFul = receivedResult?.let { it as Boolean? } == true
stopEngine(if (wasSuccessFul) Result.success() else Result.retry())
val wasSuccessful = receivedResult as? Boolean == true
stopEngine(if (wasSuccessful) Result.success() else Result.retry())
}
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel

/**
* A Flutter plugin that provides a foreground channel for workmanager operations.
*
* This implementation uses Flutter's v2 embedding API.
*/
class WorkmanagerPlugin : FlutterPlugin {
private var methodChannel: MethodChannel? = null
private var workmanagerCallHandler: WorkmanagerCallHandler? = null
Expand Down
16 changes: 9 additions & 7 deletions workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
}

// If frequency is not provided it will default to 15 minutes
schedulePeriodicTask(taskIdentifier: task.identifier, earliestBeginInSeconds: earliestBeginInSeconds ?? (15 * 60))

Check warning on line 120 in workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift

View workflow job for this annotation

GitHub Actions / format_swift

Line Length Violation: Line should be 120 characters or less; currently it has 122 characters (line_length)

let operationQueue = OperationQueue()
// Create an operation that performs the main part of the background task
Expand All @@ -144,7 +144,7 @@

/// Immediately starts a one off task
@available(iOS 13.0, *)
public static func startOneOffTask(identifier: String, taskIdentifier: UIBackgroundTaskIdentifier, inputData: String, delaySeconds: Int64) {

Check warning on line 147 in workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift

View workflow job for this annotation

GitHub Actions / format_swift

Line Length Violation: Line should be 120 characters or less; currently it has 144 characters (line_length)
let operationQueue = OperationQueue()
// Create an operation that performs the main part of the background task
let operation = BackgroundTaskOperation(
Expand Down Expand Up @@ -233,8 +233,8 @@
try BGTaskScheduler.shared.submit(request)
logInfo("BGProcessingTask submitted \(uniqueTaskIdentifier) earliestBeginInSeconds:\(begin)")
} catch {
logInfo("Could not schedule BGProcessingTask identifier:\(uniqueTaskIdentifier) error:\(error.localizedDescription)")

Check warning on line 236 in workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift

View workflow job for this annotation

GitHub Actions / format_swift

Line Length Violation: Line should be 120 characters or less; currently it has 129 characters (line_length)
logInfo("Possible issues can be: running on a simulator instead of a real device, or the task name is not registered")

Check warning on line 237 in workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift

View workflow job for this annotation

GitHub Actions / format_swift

Line Length Violation: Line should be 120 characters or less; currently it has 130 characters (line_length)
}
}

Expand Down Expand Up @@ -325,8 +325,8 @@
let inputData =
arguments[method.Arguments.inputData.rawValue] as? String

taskIdentifier = UIApplication.shared.beginBackgroundTask(withName: uniqueTaskIdentifier, expirationHandler: {

Check warning on line 328 in workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift

View workflow job for this annotation

GitHub Actions / format_swift

Line Length Violation: Line should be 120 characters or less; currently it has 122 characters (line_length)
// Mark the task as ended if time is expired, otherwise iOS might terminate and will throttle future executions

Check warning on line 329 in workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift

View workflow job for this annotation

GitHub Actions / format_swift

Line Length Violation: Line should be 120 characters or less; currently it has 127 characters (line_length)
UIApplication.shared.endBackgroundTask(taskIdentifier)
})
SwiftWorkmanagerPlugin.startOneOffTask(identifier: uniqueTaskIdentifier,
Expand Down Expand Up @@ -395,8 +395,8 @@
SwiftWorkmanagerPlugin.scheduleBackgroundProcessingTask(
withIdentifier: uniqueTaskIdentifier,
earliestBeginInSeconds: delaySeconds,
requiresNetworkConnectivity: requiresCharging,
requiresExternalPower: requiresNetwork)
requiresNetworkConnectivity: requiresNetwork,
requiresExternalPower: requiresCharging)

result(true)
return
Expand Down Expand Up @@ -456,15 +456,17 @@
if #available(iOS 13.0, *) {
BGTaskScheduler.shared.getPendingTaskRequests { taskRequests in
if taskRequests.isEmpty {
print("[BGTaskScheduler] There are no scheduled tasks")
result(true)
let message = "[BGTaskScheduler] There are no scheduled tasks"
os_log("%{public}@", log: OSLog.default, type: .debug, message)
result(message)
return
}
print("[BGTaskScheduler] Scheduled Tasks:")
var message = "[BGTaskScheduler] Scheduled Tasks:"
for taskRequest in taskRequests {
print("[BGTaskScheduler] Task Identifier: \(taskRequest.identifier) earliestBeginDate: \(taskRequest.earliestBeginDate?.formatted() ?? "")")
message += "\n[BGTaskScheduler] Task Identifier: \(taskRequest.identifier) earliestBeginDate: \(taskRequest.earliestBeginDate?.formatted() ?? "")"
}
result(true)
os_log("%{public}@", log: OSLog.default, type: .debug, message)
result(message)
}
} else {
result(FlutterError(code: "99",
Expand Down Expand Up @@ -492,4 +494,4 @@
return worker.performBackgroundRequest(completionHandler)
}

}

Check warning on line 497 in workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift

View workflow job for this annotation

GitHub Actions / format_swift

File Length Violation: File should contain 400 lines or less: currently contains 497 (file_length)
23 changes: 23 additions & 0 deletions workmanager/ios/Resources/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>
1 change: 1 addition & 0 deletions workmanager/ios/workmanager.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ Flutter Android Workmanager
s.ios.deployment_target = '13.0'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.swift_version = '5.0'
s.resource_bundles = { 'flutter_workmanager_privacy' => ['Resources/PrivacyInfo.xcprivacy'] }
end

28 changes: 28 additions & 0 deletions workmanager/ios/workmanager/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "workmanager",
platforms: [
.iOS("13.0")
],
products: [
.library(name: "workmanager", targets: ["workmanager"])
],
dependencies: [
.package(url: "https://github.com/flutter/engine", from: "0.0.0")
],
targets: [
.target(
name: "workmanager",
dependencies: [
.product(name: "Flutter", package: "engine")
],
resources: [
.process("Resources")
]
)
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// BackgroundTaskOperation.swift
// workmanager
//
// Created by Sebastian Roth on 10/06/2021.
//

import Foundation

class BackgroundTaskOperation: Operation {

private let identifier: String
private let flutterPluginRegistrantCallback: FlutterPluginRegistrantCallback?
private let inputData: String
private let backgroundMode: BackgroundMode

init(_ identifier: String,
inputData: String,
flutterPluginRegistrantCallback: FlutterPluginRegistrantCallback?,
backgroundMode: BackgroundMode) {
self.identifier = identifier
self.inputData = inputData
self.flutterPluginRegistrantCallback = flutterPluginRegistrantCallback
self.backgroundMode = backgroundMode
}

override func main() {
let semaphore = DispatchSemaphore(value: 0)
let worker = BackgroundWorker(mode: self.backgroundMode,
inputData: self.inputData,
flutterPluginRegistrantCallback: self.flutterPluginRegistrantCallback)
DispatchQueue.main.async {
worker.performBackgroundRequest { _ in
semaphore.signal()
}
}

semaphore.wait()
}
}
140 changes: 140 additions & 0 deletions workmanager/ios/workmanager/Sources/workmanager/BackgroundWorker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//
// BackgroundWorker.swift
// workmanager
//
// Created by Sebastian Roth on 10/06/2021.
//

import Foundation

enum BackgroundMode {
case backgroundFetch
case backgroundProcessingTask(identifier: String)
case backgroundPeriodicTask(identifier: String)
case backgroundOneOffTask(identifier: String)

var flutterThreadlabelPrefix: String {
switch self {
case .backgroundFetch:
return "\(SwiftWorkmanagerPlugin.identifier).BackgroundFetch"
case .backgroundProcessingTask:
return "\(SwiftWorkmanagerPlugin.identifier).BackgroundProcessingTask"
case .backgroundPeriodicTask:
return "\(SwiftWorkmanagerPlugin.identifier).BackgroundPeriodicTask"
case .backgroundOneOffTask:
return "\(SwiftWorkmanagerPlugin.identifier).OneOffTask"
}
}

var onResultSendArguments: [String: String] {
switch self {
case .backgroundFetch:
return ["\(SwiftWorkmanagerPlugin.identifier).DART_TASK": "iOSPerformFetch"]
case let .backgroundProcessingTask(identifier):
return ["\(SwiftWorkmanagerPlugin.identifier).DART_TASK": identifier]
case let .backgroundPeriodicTask(identifier):
return ["\(SwiftWorkmanagerPlugin.identifier).DART_TASK": identifier]
case let .backgroundOneOffTask(identifier):
return ["\(SwiftWorkmanagerPlugin.identifier).DART_TASK": identifier]
}
}
}

class BackgroundWorker {

let backgroundMode: BackgroundMode
let flutterPluginRegistrantCallback: FlutterPluginRegistrantCallback?
let inputData: String

init(mode: BackgroundMode, inputData: String, flutterPluginRegistrantCallback: FlutterPluginRegistrantCallback?) {
backgroundMode = mode
self.inputData = inputData
self.flutterPluginRegistrantCallback = flutterPluginRegistrantCallback
}

private struct BackgroundChannel {
static let name = "\(SwiftWorkmanagerPlugin.identifier)/background_channel_work_manager"
static let initialized = "backgroundChannelInitialized"
static let onResultSendCommand = "onResultSend"
}

/// The result is discardable due to how [BackgroundTaskOperation] works.
@discardableResult
func performBackgroundRequest(_ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) -> Bool {
guard let callbackHandle = UserDefaultsHelper.getStoredCallbackHandle(),
let flutterCallbackInformation = FlutterCallbackCache.lookupCallbackInformation(callbackHandle)
else {
logError("[\(String(describing: self))] \(WMPError.workmanagerNotInitialized.message)")
completionHandler(.failed)
return false
}

let taskSessionStart = Date()
let taskSessionIdentifier = UUID()

let debugHelper = DebugNotificationHelper(taskSessionIdentifier)
debugHelper.showStartFetchNotification(
startDate: taskSessionStart,
callBackHandle: callbackHandle,
callbackInfo: flutterCallbackInformation
)

var flutterEngine: FlutterEngine? = FlutterEngine(
name: backgroundMode.flutterThreadlabelPrefix,
project: nil,
allowHeadlessExecution: true
)

flutterEngine!.run(
withEntrypoint: flutterCallbackInformation.callbackName,
libraryURI: flutterCallbackInformation.callbackLibraryPath
)
flutterPluginRegistrantCallback?(flutterEngine!)

var backgroundMethodChannel: FlutterMethodChannel? = FlutterMethodChannel(
name: BackgroundChannel.name,
binaryMessenger: flutterEngine!.binaryMessenger
)

func cleanupFlutterResources() {
flutterEngine?.destroyContext()
backgroundMethodChannel = nil
flutterEngine = nil
}

backgroundMethodChannel?.setMethodCallHandler { call, result in
switch call.method {
case BackgroundChannel.initialized:
result(true) // Agree to Flutter's method invocation
var arguments = self.backgroundMode.onResultSendArguments
if self.inputData != "" {
arguments = arguments.merging(["be.tramckrijte.workmanager.INPUT_DATA": self.inputData]) { current, _ in current }
}

backgroundMethodChannel?.invokeMethod(
BackgroundChannel.onResultSendCommand,
arguments: arguments,
result: { flutterResult in
cleanupFlutterResources()
let taskSessionCompleter = Date()
let result: UIBackgroundFetchResult = (flutterResult as? Bool ?? false) ? .newData : .failed
let taskDuration = taskSessionCompleter.timeIntervalSince(taskSessionStart)
logInfo("[\(String(describing: self))] \(#function) -> performBackgroundRequest.\(result) (finished in \(taskDuration.formatToSeconds()))")

debugHelper.showCompletedFetchNotification(
completedDate: taskSessionCompleter,
result: result,
elapsedTime: taskDuration
)
completionHandler(result)
})
default:
result(WMPError.unhandledMethod(call.method).asFlutterError)
cleanupFlutterResources()
completionHandler(UIBackgroundFetchResult.failed)
}
}

return true
}
}
Loading
Loading