Skip to content

Commit ef82797

Browse files
enedclaude
andcommitted
chore: finalize hook-based debug system implementation
All GitHub Actions fixes applied and both example builds working: - iOS 14.0 deployment target set across all components - Android TaskStatus imports resolved - iOS internal type visibility fixed - Comprehensive pre-commit requirements documented - Both Android APK and iOS app build successfully 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 4bad475 commit ef82797

File tree

11 files changed

+160
-91
lines changed

11 files changed

+160
-91
lines changed

CLAUDE.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
## Pre-Commit Requirements
22
**CRITICAL**: Always run from project root before ANY commit:
33
1. `dart analyze` (check for code errors)
4-
2. `ktlint -F .`
5-
3. `find . -name "*.dart" ! -name "*.g.dart" ! -path "*/.*" -print0 | xargs -0 dart format --set-exit-if-changed`
6-
4. `flutter test` (all Dart tests)
7-
5. `cd example/android && ./gradlew :workmanager_android:test` (Android native tests)
4+
2. `ktlint -F .` (format Kotlin code)
5+
3. `swiftlint --fix` (format Swift code)
6+
4. `find . -name "*.dart" ! -name "*.g.dart" ! -path "*/.*" -print0 | xargs -0 dart format --set-exit-if-changed`
7+
5. `flutter test` (all Dart tests)
8+
6. `cd example/android && ./gradlew :workmanager_android:test` (Android native tests)
9+
7. `cd example && flutter build apk --debug` (build Android example app)
10+
8. `cd example && flutter build ios --debug --no-codesign` (build iOS example app)
811

912
## Code Generation
1013
- Regenerate Pigeon files: `melos run generate:pigeon`
@@ -46,4 +49,18 @@
4649
</Tabs>
4750
```
4851
- Use `<TabItem>` not `<Tab>` - this is a common mistake that causes JavaScript errors
49-
- Always include both `label` and `value` props on TabItem components
52+
- Always include both `label` and `value` props on TabItem components
53+
54+
## Pull Request Description Guidelines
55+
56+
Template:
57+
```markdown
58+
## Summary
59+
- Brief change description
60+
61+
Fixes #123
62+
63+
## Breaking Changes (if applicable)
64+
**Before:** `old code`
65+
**After:** `new code`
66+
```

workmanager/lib/src/workmanager_impl.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,17 @@ 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-
///
121+
///
122122
/// [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.')
123+
Future<void> initialize(
124+
Function callbackDispatcher, {
125+
@Deprecated(
126+
'Use WorkmanagerDebug handlers instead. This parameter has no effect.')
125127
bool isInDebugMode = false,
126128
}) async {
127129
// ignore: deprecated_member_use
128-
return _platform.initialize(callbackDispatcher, isInDebugMode: isInDebugMode);
130+
return _platform.initialize(callbackDispatcher,
131+
isInDebugMode: isInDebugMode);
129132
}
130133

131134
/// This method needs to be called from within your [callbackDispatcher].

workmanager/test/backward_compatibility_test.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ void main() {
1010
test('initialize() still accepts isInDebugMode parameter', () async {
1111
// This test verifies that existing code using isInDebugMode will still compile
1212
// The parameter is deprecated but should not break existing code
13-
13+
1414
// This should compile without errors
1515
await expectLater(
1616
() async => await Workmanager().initialize(
@@ -20,12 +20,12 @@ void main() {
2020
),
2121
throwsA(isA<UnimplementedError>()), // Platform not available in tests
2222
);
23-
23+
2424
// This should also compile (without the parameter)
2525
await expectLater(
2626
() async => await Workmanager().initialize(callbackDispatcher),
2727
throwsA(isA<UnimplementedError>()), // Platform not available in tests
2828
);
2929
});
3030
});
31-
}
31+
}

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

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.work.ListenableWorker
99
import androidx.work.WorkerParameters
1010
import com.google.common.util.concurrent.ListenableFuture
1111
import dev.fluttercommunity.workmanager.pigeon.WorkmanagerFlutterApi
12+
import dev.fluttercommunity.workmanager.pigeon.TaskStatus
1213
import io.flutter.embedding.engine.FlutterEngine
1314
import io.flutter.embedding.engine.dart.DartExecutor
1415
import io.flutter.embedding.engine.loader.FlutterLoader
@@ -82,22 +83,23 @@ class BackgroundWorker(
8283

8384
if (callbackInfo == null) {
8485
val exception = IllegalStateException("Failed to resolve Dart callback for handle $callbackHandle")
85-
Log.e(TAG, exception.message)
86+
Log.e(TAG, exception.message ?: "Unknown error")
8687
WorkmanagerDebug.onExceptionEncountered(applicationContext, null, exception)
8788
completer?.set(Result.failure())
8889
return@ensureInitializationCompleteAsync
8990
}
9091

9192
val dartBundlePath = flutterLoader.findAppBundlePath()
9293

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

103105
engine?.let { engine ->
@@ -129,18 +131,20 @@ class BackgroundWorker(
129131
private fun stopEngine(result: Result?) {
130132
val fetchDuration = System.currentTimeMillis() - startTime
131133

132-
val taskInfo = TaskDebugInfo(
133-
taskName = dartTask,
134-
inputData = payload,
135-
startTime = startTime,
136-
)
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-
134+
val taskInfo =
135+
TaskDebugInfo(
136+
taskName = dartTask,
137+
inputData = payload,
138+
startTime = startTime,
139+
)
140+
141+
val taskResult =
142+
TaskResult(
143+
success = result is Result.Success,
144+
duration = fetchDuration,
145+
error = if (result is Result.Failure) "Task failed" else null,
146+
)
147+
144148
val status = if (result is Result.Success) TaskStatus.COMPLETED else TaskStatus.FAILED
145149
WorkmanagerDebug.onTaskStatusUpdate(applicationContext, taskInfo, status, taskResult)
146150

@@ -171,11 +175,12 @@ class BackgroundWorker(
171175
val exception = result.exceptionOrNull()
172176
Log.e(TAG, "Error executing task: ${exception?.message}")
173177
if (exception != null) {
174-
val taskInfo = TaskDebugInfo(
175-
taskName = dartTask,
176-
inputData = payload,
177-
startTime = startTime
178-
)
178+
val taskInfo =
179+
TaskDebugInfo(
180+
taskName = dartTask,
181+
inputData = payload,
182+
startTime = startTime,
183+
)
179184
WorkmanagerDebug.onExceptionEncountered(applicationContext, taskInfo, exception)
180185
}
181186
stopEngine(Result.failure())

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dev.fluttercommunity.workmanager
22

33
import android.content.Context
44
import android.util.Log
5+
import dev.fluttercommunity.workmanager.pigeon.TaskStatus
56

67
/**
78
* A debug handler that outputs debug information to Android's Log system.
@@ -11,7 +12,12 @@ class LoggingDebugHandler : WorkmanagerDebug() {
1112
private const val TAG = "WorkmanagerDebug"
1213
}
1314

14-
override fun onTaskStatusUpdate(context: Context, taskInfo: TaskDebugInfo, status: TaskStatus, result: TaskResult?) {
15+
override fun onTaskStatusUpdate(
16+
context: Context,
17+
taskInfo: TaskDebugInfo,
18+
status: TaskStatus,
19+
result: TaskResult?,
20+
) {
1521
when (status) {
1622
TaskStatus.SCHEDULED -> Log.d(TAG, "Task scheduled: ${taskInfo.taskName}")
1723
TaskStatus.STARTED -> Log.d(TAG, "Task started: ${taskInfo.taskName}, callbackHandle: ${taskInfo.callbackHandle}")
@@ -29,7 +35,11 @@ class LoggingDebugHandler : WorkmanagerDebug() {
2935
}
3036
}
3137

32-
override fun onExceptionEncountered(context: Context, taskInfo: TaskDebugInfo?, exception: Throwable) {
38+
override fun onExceptionEncountered(
39+
context: Context,
40+
taskInfo: TaskDebugInfo?,
41+
exception: Throwable,
42+
) {
3343
val taskName = taskInfo?.taskName ?: "unknown"
3444
Log.e(TAG, "Exception in task: $taskName", exception)
3545
}

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

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.app.NotificationManager
55
import android.content.Context
66
import android.os.Build
77
import androidx.core.app.NotificationCompat
8+
import dev.fluttercommunity.workmanager.pigeon.TaskStatus
89
import java.text.DateFormat
910
import java.util.Date
1011
import java.util.concurrent.TimeUnit.MILLISECONDS
@@ -28,61 +29,76 @@ class NotificationDebugHandler : WorkmanagerDebug() {
2829
private val warningEmoji = "⚠️"
2930
private val currentTime get() = debugDateFormatter.format(Date())
3031

31-
override fun onTaskStatusUpdate(context: Context, taskInfo: TaskDebugInfo, status: TaskStatus, result: TaskResult?) {
32+
override fun onTaskStatusUpdate(
33+
context: Context,
34+
taskInfo: TaskDebugInfo,
35+
status: TaskStatus,
36+
result: TaskResult?,
37+
) {
3238
val notificationId = Random.nextInt()
33-
val (emoji, title, content) = when (status) {
34-
TaskStatus.SCHEDULED -> Triple(
35-
"📅",
36-
"Task Scheduled",
37-
"• Task: ${taskInfo.taskName}\n• Input Data: ${taskInfo.inputData ?: "none"}"
38-
)
39-
TaskStatus.STARTED -> Triple(
40-
workEmoji,
41-
"Task Starting",
42-
"• Task: ${taskInfo.taskName}\n• Callback Handle: ${taskInfo.callbackHandle}"
43-
)
44-
TaskStatus.COMPLETED -> {
45-
val success = result?.success ?: false
46-
val duration = MILLISECONDS.toSeconds(result?.duration ?: 0)
47-
Triple(
48-
if (success) successEmoji else failureEmoji,
49-
if (success) "Task Completed" else "Task Failed",
50-
"• Task: ${taskInfo.taskName}\n• Duration: ${duration}s${if (result?.error != null) "\n• Error: ${result.error}" else ""}"
51-
)
39+
val (emoji, title, content) =
40+
when (status) {
41+
TaskStatus.SCHEDULED ->
42+
Triple(
43+
"📅",
44+
"Task Scheduled",
45+
"• Task: ${taskInfo.taskName}\n• Input Data: ${taskInfo.inputData ?: "none"}",
46+
)
47+
TaskStatus.STARTED ->
48+
Triple(
49+
workEmoji,
50+
"Task Starting",
51+
"• Task: ${taskInfo.taskName}\n• Callback Handle: ${taskInfo.callbackHandle}",
52+
)
53+
TaskStatus.COMPLETED -> {
54+
val success = result?.success ?: false
55+
val duration = MILLISECONDS.toSeconds(result?.duration ?: 0)
56+
Triple(
57+
if (success) successEmoji else failureEmoji,
58+
if (success) "Task Completed" else "Task Failed",
59+
"• Task: ${taskInfo.taskName}\n• Duration: ${duration}s${if (result?.error != null) "\n• Error: ${result.error}" else ""}",
60+
)
61+
}
62+
TaskStatus.FAILED ->
63+
Triple(
64+
failureEmoji,
65+
"Task Failed",
66+
"• Task: ${taskInfo.taskName}\n• Error: ${result?.error ?: "Unknown error"}",
67+
)
68+
TaskStatus.CANCELLED ->
69+
Triple(
70+
warningEmoji,
71+
"Task Cancelled",
72+
"• Task: ${taskInfo.taskName}",
73+
)
74+
TaskStatus.RETRYING ->
75+
Triple(
76+
"🔄",
77+
"Task Retrying",
78+
"• Task: ${taskInfo.taskName}",
79+
)
5280
}
53-
TaskStatus.FAILED -> Triple(
54-
failureEmoji,
55-
"Task Failed",
56-
"• Task: ${taskInfo.taskName}\n• Error: ${result?.error ?: "Unknown error"}"
57-
)
58-
TaskStatus.CANCELLED -> Triple(
59-
warningEmoji,
60-
"Task Cancelled",
61-
"• Task: ${taskInfo.taskName}"
62-
)
63-
TaskStatus.RETRYING -> Triple(
64-
"🔄",
65-
"Task Retrying",
66-
"• Task: ${taskInfo.taskName}"
67-
)
68-
}
6981

7082
postNotification(
7183
context,
7284
notificationId,
7385
"$emoji $currentTime",
74-
"$title\n$content"
86+
"$title\n$content",
7587
)
7688
}
7789

78-
override fun onExceptionEncountered(context: Context, taskInfo: TaskDebugInfo?, exception: Throwable) {
90+
override fun onExceptionEncountered(
91+
context: Context,
92+
taskInfo: TaskDebugInfo?,
93+
exception: Throwable,
94+
) {
7995
val notificationId = Random.nextInt()
8096
val taskName = taskInfo?.taskName ?: "unknown"
8197
postNotification(
8298
context,
8399
notificationId,
84100
"$failureEmoji $currentTime",
85-
"Exception in Task\n• Task: $taskName\n• Error: ${exception.message}"
101+
"Exception in Task\n• Task: $taskName\n• Error: ${exception.message}",
86102
)
87103
}
88104

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,44 @@ abstract class WorkmanagerDebug {
4848
fun getCurrent(): WorkmanagerDebug = current
4949

5050
// Internal methods for the plugin to call
51-
internal fun onTaskStatusUpdate(context: Context, taskInfo: TaskDebugInfo, status: TaskStatus, result: TaskResult? = null) {
51+
internal fun onTaskStatusUpdate(
52+
context: Context,
53+
taskInfo: TaskDebugInfo,
54+
status: TaskStatus,
55+
result: TaskResult? = null,
56+
) {
5257
current.onTaskStatusUpdate(context, taskInfo, status, result)
5358
}
5459

55-
internal fun onExceptionEncountered(context: Context, taskInfo: TaskDebugInfo?, exception: Throwable) {
60+
internal fun onExceptionEncountered(
61+
context: Context,
62+
taskInfo: TaskDebugInfo?,
63+
exception: Throwable,
64+
) {
5665
current.onExceptionEncountered(context, taskInfo, exception)
5766
}
5867
}
5968

6069
/**
6170
* Called when a task status changes.
6271
*/
63-
open fun onTaskStatusUpdate(context: Context, taskInfo: TaskDebugInfo, status: TaskStatus, result: TaskResult?) {
72+
open fun onTaskStatusUpdate(
73+
context: Context,
74+
taskInfo: TaskDebugInfo,
75+
status: TaskStatus,
76+
result: TaskResult?,
77+
) {
6478
// Default: do nothing
6579
}
6680

6781
/**
6882
* Called when an exception occurs during task processing.
6983
*/
70-
open fun onExceptionEncountered(context: Context, taskInfo: TaskDebugInfo?, exception: Throwable) {
84+
open fun onExceptionEncountered(
85+
context: Context,
86+
taskInfo: TaskDebugInfo?,
87+
exception: Throwable,
88+
) {
7189
// Default: do nothing
7290
}
7391
}

0 commit comments

Comments
 (0)