Skip to content

Commit 6e8a516

Browse files
enedclaude
andcommitted
feat: finalize hook-based debug system with cross-platform TaskStatus enum
- Move TaskStatus enum to Pigeon for consistent cross-platform types - Regenerate all Pigeon-generated files with TaskStatus enum - Update mocks to include deprecated isInDebugMode parameter - Add backward compatibility test ensuring deprecated parameter still compiles - Update CLAUDE.md to document code generation workflow All tests passing and hook-based debug system is complete with: - Abstract WorkmanagerDebug class with static current handler - onTaskStatusUpdate and onExceptionEncountered methods - Complete platform consistency between Android and iOS - Backward compatibility maintained through deprecation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 6681929 commit 6e8a516

21 files changed

+496
-310
lines changed

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Regenerate Pigeon files: `melos run generate:pigeon`
1111
- Regenerate Dart files (including mocks): `melos run generate:dart`
1212
- Do not manually edit *.g.* files
13+
- Never manually modify mocks or generated files. Always modify the source, then run the generator tasks via melos.
1314

1415
## Running Tests
1516
- Use melos to run all tests: `melos run test`

docs/debugging.mdx

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,14 @@ Then set up platform-specific debug handlers as needed.
2626
Shows debug information in Android's Log system (visible in `adb logcat`):
2727

2828
```kotlin
29-
// In your MainActivity.kt
29+
// In your Application class
3030
import dev.fluttercommunity.workmanager.WorkmanagerDebug
3131
import dev.fluttercommunity.workmanager.LoggingDebugHandler
3232

33-
class MainActivity : FlutterActivity() {
34-
override fun onCreate(savedInstanceState: Bundle?) {
35-
super.onCreate(savedInstanceState)
36-
37-
// Enable logging debug handler
38-
WorkmanagerDebug.setDebugHandler(LoggingDebugHandler())
33+
class MyApplication : Application() {
34+
override fun onCreate() {
35+
super.onCreate()
36+
WorkmanagerDebug.setCurrent(LoggingDebugHandler())
3937
}
4038
}
4139
```
@@ -47,8 +45,12 @@ Shows debug information as notifications (requires notification permissions):
4745
```kotlin
4846
import dev.fluttercommunity.workmanager.NotificationDebugHandler
4947

50-
// Enable notification debug handler
51-
WorkmanagerDebug.setDebugHandler(NotificationDebugHandler())
48+
class MyApplication : Application() {
49+
override fun onCreate() {
50+
super.onCreate()
51+
WorkmanagerDebug.setCurrent(NotificationDebugHandler())
52+
}
53+
}
5254
```
5355

5456
## iOS Debug Handlers
@@ -67,9 +69,7 @@ class AppDelegate: FlutterAppDelegate {
6769
_ application: UIApplication,
6870
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
6971
) -> Bool {
70-
// Enable logging debug handler
71-
WorkmanagerDebug.setDebugHandler(LoggingDebugHandler())
72-
72+
WorkmanagerDebug.setCurrent(LoggingDebugHandler())
7373
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
7474
}
7575
}
@@ -80,8 +80,7 @@ class AppDelegate: FlutterAppDelegate {
8080
Shows debug information as notifications:
8181

8282
```swift
83-
// Enable notification debug handler
84-
WorkmanagerDebug.setDebugHandler(NotificationDebugHandler())
83+
WorkmanagerDebug.setCurrent(NotificationDebugHandler())
8584
```
8685

8786
## Custom Debug Handlers
@@ -92,34 +91,44 @@ Create your own debug handler for custom logging needs:
9291
<TabItem label="Android" value="android">
9392

9493
```kotlin
95-
class CustomDebugHandler : WorkmanagerDebugHandler {
96-
override fun onTaskStarting(context: Context, taskInfo: TaskDebugInfo) {
97-
// Your custom logic (analytics, file logging, etc.)
94+
class CustomDebugHandler : WorkmanagerDebug() {
95+
override fun onTaskStatusUpdate(context: Context, taskInfo: TaskDebugInfo, status: TaskStatus, result: TaskResult?) {
96+
when (status) {
97+
TaskStatus.STARTED -> // Task started logic
98+
TaskStatus.COMPLETED -> // Task completed logic
99+
// Handle other statuses
100+
}
98101
}
99102

100-
override fun onTaskCompleted(context: Context, taskInfo: TaskDebugInfo, result: TaskResult) {
101-
// Your custom completion logic
103+
override fun onExceptionEncountered(context: Context, taskInfo: TaskDebugInfo?, exception: Throwable) {
104+
// Handle exceptions
102105
}
103106
}
104107

105-
WorkmanagerDebug.setDebugHandler(CustomDebugHandler())
108+
WorkmanagerDebug.setCurrent(CustomDebugHandler())
106109
```
107110

108111
</TabItem>
109112
<TabItem label="iOS" value="ios">
110113

111114
```swift
112-
class CustomDebugHandler: WorkmanagerDebugHandler {
113-
func onTaskStarting(taskInfo: TaskDebugInfo) {
114-
// Your custom logic
115+
class CustomDebugHandler: WorkmanagerDebug {
116+
override func onTaskStatusUpdate(taskInfo: TaskDebugInfo, status: TaskStatus, result: TaskResult?) {
117+
switch status {
118+
case .started:
119+
// Task started logic
120+
case .completed:
121+
// Task completed logic
122+
// Handle other statuses
123+
}
115124
}
116125

117-
func onTaskCompleted(taskInfo: TaskDebugInfo, result: TaskResult) {
118-
// Your custom completion logic
126+
override func onExceptionEncountered(taskInfo: TaskDebugInfo?, exception: Error) {
127+
// Handle exceptions
119128
}
120129
}
121130

122-
WorkmanagerDebug.setDebugHandler(CustomDebugHandler())
131+
WorkmanagerDebug.setCurrent(CustomDebugHandler())
123132
```
124133

125134
</TabItem>

workmanager/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Future
22

33
## Breaking Changes
4-
* **BREAKING**: Removed `isInDebugMode` parameter from `initialize()` method
4+
* **BREAKING**: The `isInDebugMode` parameter in `initialize()` is now deprecated and has no effect
5+
* The parameter is still accepted for backward compatibility but will be removed in a future version
56
* Replace with new hook-based debug system for better flexibility
67
* See updated debugging documentation for migration guide and usage examples
78
* No debug output by default - add platform-specific debug handlers as needed

workmanager/lib/src/workmanager_impl.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,14 @@ class Workmanager {
118118
/// Initialize the Workmanager with a [callbackDispatcher].
119119
///
120120
/// The [callbackDispatcher] is a top level function which will be invoked by Android or iOS whenever a scheduled task is due.
121-
Future<void> initialize(Function callbackDispatcher) async {
122-
return _platform.initialize(callbackDispatcher);
121+
///
122+
/// [isInDebugMode] is deprecated and has no effect. Use WorkmanagerDebug handlers instead.
123+
Future<void> initialize(Function callbackDispatcher, {
124+
@Deprecated('Use WorkmanagerDebug handlers instead. This parameter has no effect.')
125+
bool isInDebugMode = false,
126+
}) async {
127+
// ignore: deprecated_member_use
128+
return _platform.initialize(callbackDispatcher, isInDebugMode: isInDebugMode);
123129
}
124130

125131
/// This method needs to be called from within your [callbackDispatcher].
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:workmanager/workmanager.dart';
3+
4+
void callbackDispatcher() {
5+
// Test callback dispatcher
6+
}
7+
8+
void main() {
9+
group('Backward compatibility', () {
10+
test('initialize() still accepts isInDebugMode parameter', () async {
11+
// This test verifies that existing code using isInDebugMode will still compile
12+
// The parameter is deprecated but should not break existing code
13+
14+
// This should compile without errors
15+
await expectLater(
16+
() async => await Workmanager().initialize(
17+
callbackDispatcher,
18+
// ignore: deprecated_member_use_from_same_package
19+
isInDebugMode: true, // Deprecated but still compiles
20+
),
21+
throwsA(isA<UnimplementedError>()), // Platform not available in tests
22+
);
23+
24+
// This should also compile (without the parameter)
25+
await expectLater(
26+
() async => await Workmanager().initialize(callbackDispatcher),
27+
throwsA(isA<UnimplementedError>()), // Platform not available in tests
28+
);
29+
});
30+
});
31+
}

workmanager/test/workmanager_test.mocks.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ class MockWorkmanager extends _i1.Mock implements _i2.Workmanager {
3535

3636
@override
3737
_i3.Future<void> initialize(
38-
Function? callbackDispatcher,
39-
) =>
38+
Function? callbackDispatcher, {
39+
bool? isInDebugMode = false,
40+
}) =>
4041
(super.noSuchMethod(
4142
Invocation.method(
4243
#initialize,
4344
[callbackDispatcher],
45+
{#isInDebugMode: isInDebugMode},
4446
),
4547
returnValue: _i3.Future<void>.value(),
4648
returnValueForMissingStub: _i3.Future<void>.value(),

workmanager_android/android/src/main/kotlin/dev/fluttercommunity/workmanager/BackgroundWorker.kt

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,24 @@ class BackgroundWorker(
8181
val callbackInfo = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle)
8282

8383
if (callbackInfo == null) {
84-
Log.e(TAG, "Failed to resolve Dart callback for handle $callbackHandle.")
84+
val exception = IllegalStateException("Failed to resolve Dart callback for handle $callbackHandle")
85+
Log.e(TAG, exception.message)
86+
WorkmanagerDebug.onExceptionEncountered(applicationContext, null, exception)
8587
completer?.set(Result.failure())
8688
return@ensureInitializationCompleteAsync
8789
}
8890

8991
val dartBundlePath = flutterLoader.findAppBundlePath()
9092

91-
WorkmanagerDebug.onTaskStarting(
92-
applicationContext,
93-
TaskDebugInfo(
94-
taskName = dartTask,
95-
inputData = payload,
96-
startTime = startTime,
97-
callbackHandle = callbackHandle,
98-
callbackInfo = callbackInfo?.callbackName,
99-
),
93+
val taskInfo = TaskDebugInfo(
94+
taskName = dartTask,
95+
inputData = payload,
96+
startTime = startTime,
97+
callbackHandle = callbackHandle,
98+
callbackInfo = callbackInfo?.callbackName,
10099
)
100+
101+
WorkmanagerDebug.onTaskStatusUpdate(applicationContext, taskInfo, TaskStatus.STARTED)
101102

102103
engine?.let { engine ->
103104
flutterApi = WorkmanagerFlutterApi(engine.dartExecutor.binaryMessenger)
@@ -128,19 +129,20 @@ class BackgroundWorker(
128129
private fun stopEngine(result: Result?) {
129130
val fetchDuration = System.currentTimeMillis() - startTime
130131

131-
WorkmanagerDebug.onTaskCompleted(
132-
applicationContext,
133-
TaskDebugInfo(
134-
taskName = dartTask,
135-
inputData = payload,
136-
startTime = startTime,
137-
),
138-
TaskResult(
139-
success = result is Result.Success,
140-
duration = fetchDuration,
141-
error = if (result is Result.Failure) "Task failed" else null,
142-
),
132+
val taskInfo = TaskDebugInfo(
133+
taskName = dartTask,
134+
inputData = payload,
135+
startTime = startTime,
143136
)
137+
138+
val taskResult = TaskResult(
139+
success = result is Result.Success,
140+
duration = fetchDuration,
141+
error = if (result is Result.Failure) "Task failed" else null,
142+
)
143+
144+
val status = if (result is Result.Success) TaskStatus.COMPLETED else TaskStatus.FAILED
145+
WorkmanagerDebug.onTaskStatusUpdate(applicationContext, taskInfo, status, taskResult)
144146

145147
// No result indicates we were signalled to stop by WorkManager. The result is already
146148
// STOPPED, so no need to resolve another one.
@@ -166,7 +168,16 @@ class BackgroundWorker(
166168
stopEngine(if (wasSuccessful) Result.success() else Result.retry())
167169
}
168170
result.isFailure -> {
169-
Log.e(TAG, "Error executing task: ${result.exceptionOrNull()?.message}")
171+
val exception = result.exceptionOrNull()
172+
Log.e(TAG, "Error executing task: ${exception?.message}")
173+
if (exception != null) {
174+
val taskInfo = TaskDebugInfo(
175+
taskName = dartTask,
176+
inputData = payload,
177+
startTime = startTime
178+
)
179+
WorkmanagerDebug.onExceptionEncountered(applicationContext, taskInfo, exception)
180+
}
170181
stopEngine(Result.failure())
171182
}
172183
}

workmanager_android/android/src/main/kotlin/dev/fluttercommunity/workmanager/LoggingDebugHandler.kt

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,32 @@ import android.util.Log
55

66
/**
77
* A debug handler that outputs debug information to Android's Log system.
8-
* Use this for development to see task execution in the console.
98
*/
10-
class LoggingDebugHandler : WorkmanagerDebugHandler {
9+
class LoggingDebugHandler : WorkmanagerDebug() {
1110
companion object {
1211
private const val TAG = "WorkmanagerDebug"
1312
}
1413

15-
override fun onTaskStarting(
16-
context: Context,
17-
taskInfo: TaskDebugInfo,
18-
) {
19-
Log.d(TAG, "Task starting: ${taskInfo.taskName}, callbackHandle: ${taskInfo.callbackHandle}")
14+
override fun onTaskStatusUpdate(context: Context, taskInfo: TaskDebugInfo, status: TaskStatus, result: TaskResult?) {
15+
when (status) {
16+
TaskStatus.SCHEDULED -> Log.d(TAG, "Task scheduled: ${taskInfo.taskName}")
17+
TaskStatus.STARTED -> Log.d(TAG, "Task started: ${taskInfo.taskName}, callbackHandle: ${taskInfo.callbackHandle}")
18+
TaskStatus.COMPLETED -> {
19+
val success = result?.success ?: false
20+
val duration = result?.duration ?: 0
21+
Log.d(TAG, "Task completed: ${taskInfo.taskName}, success: $success, duration: ${duration}ms")
22+
}
23+
TaskStatus.FAILED -> {
24+
val error = result?.error ?: "Unknown error"
25+
Log.e(TAG, "Task failed: ${taskInfo.taskName}, error: $error")
26+
}
27+
TaskStatus.CANCELLED -> Log.w(TAG, "Task cancelled: ${taskInfo.taskName}")
28+
TaskStatus.RETRYING -> Log.w(TAG, "Task retrying: ${taskInfo.taskName}")
29+
}
2030
}
2131

22-
override fun onTaskCompleted(
23-
context: Context,
24-
taskInfo: TaskDebugInfo,
25-
result: TaskResult,
26-
) {
27-
val status = if (result.success) "SUCCESS" else "FAILURE"
28-
Log.d(TAG, "Task completed: ${taskInfo.taskName}, result: $status, duration: ${result.duration}ms")
29-
if (result.error != null) {
30-
Log.e(TAG, "Task error: ${result.error}")
31-
}
32+
override fun onExceptionEncountered(context: Context, taskInfo: TaskDebugInfo?, exception: Throwable) {
33+
val taskName = taskInfo?.taskName ?: "unknown"
34+
Log.e(TAG, "Exception in task: $taskName", exception)
3235
}
3336
}

0 commit comments

Comments
 (0)