Skip to content

Commit cd19b48

Browse files
enedclaude
andcommitted
fix: Handle inputData Map properly and add comprehensive integration tests
Android fixes: - Fix ClassCastException when extracting inputData payload - Convert Map<String, Any> to JSON string for WorkManager Data storage - Remove debug logging for cleaner implementation Integration tests: - Cover all platform methods with proper error handling - Test platform-specific features (Android vs iOS capabilities) - Handle expected iOS BGTaskScheduler errors in test environment - Test various parameter combinations and edge cases - Ensure proper cleanup in all test scenarios - Validate UnsupportedError exceptions for unsupported platform methods The tests now comprehensively cover: - initialize() with debug mode - registerOneOffTask() with all parameter variations - registerPeriodicTask() with platform-specific features - registerProcessingTask() (iOS only) - cancelByUniqueName(), cancelByTag(), cancelAll() - isScheduled() (Android only) - printScheduledTasks() (iOS only) - Complex multi-task workflows 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2d89760 commit cd19b48

2 files changed

Lines changed: 310 additions & 58 deletions

File tree

example/integration_test/workmanager_integration_test.dart

Lines changed: 299 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,63 +15,305 @@ void callbackDispatcher() {
1515
void main() {
1616
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
1717

18-
testWidgets('initialize & schedule task - android',
19-
(WidgetTester tester) async {
20-
final wm = Workmanager();
21-
await wm.initialize(callbackDispatcher);
22-
await wm.registerOneOffTask(
23-
'dev.fluttercommunity.workmanagerExample.taskId',
24-
'taskName',
25-
);
26-
}, skip: !Platform.isAndroid);
27-
28-
testWidgets('initialize & schedule task - iOS', (WidgetTester tester) async {
29-
final wm = Workmanager();
30-
await wm.initialize(callbackDispatcher);
31-
try {
32-
await wm.registerOneOffTask(
33-
'dev.fluttercommunity.workmanagerExample.taskId',
34-
'taskName',
35-
);
36-
await wm.cancelAll();
37-
} on PlatformException catch (e) {
38-
if (e.code !=
39-
'bgTaskSchedulingFailed(Error Domain=BGTaskSchedulerErrorDomain Code=1 "(null)") error') {
40-
rethrow;
18+
group('Workmanager Integration Tests', () {
19+
late Workmanager workmanager;
20+
21+
setUp(() {
22+
workmanager = Workmanager();
23+
});
24+
25+
testWidgets('initialize should succeed on all platforms', (WidgetTester tester) async {
26+
await workmanager.initialize(callbackDispatcher, isInDebugMode: true);
27+
// No exception means success
28+
});
29+
30+
testWidgets('registerOneOffTask basic should succeed', (WidgetTester tester) async {
31+
await workmanager.initialize(callbackDispatcher);
32+
33+
try {
34+
await workmanager.registerOneOffTask(
35+
'test.oneoff.basic',
36+
'basicTask',
37+
);
38+
// Clean up
39+
await workmanager.cancelByUniqueName('test.oneoff.basic');
40+
} on PlatformException catch (e) {
41+
// iOS may fail with BGTaskSchedulerErrorDomain in testing environment
42+
if (Platform.isIOS && e.code.contains('bgTaskSchedulingFailed')) {
43+
// This is expected in test environment on iOS
44+
} else {
45+
rethrow;
46+
}
4147
}
42-
}
43-
}, skip: !Platform.isIOS);
44-
45-
testWidgets('initialize & cancelAll - iOS', (WidgetTester tester) async {
46-
final wm = Workmanager();
47-
await wm.initialize(callbackDispatcher);
48-
try {
49-
await wm.cancelAll();
50-
} on PlatformException catch (e) {
51-
if (e.code !=
52-
'bgTaskSchedulingFailed(Error Domain=BGTaskSchedulerErrorDomain Code=1 "(null)") error') {
53-
rethrow;
48+
});
49+
50+
testWidgets('registerOneOffTask with inputData should succeed', (WidgetTester tester) async {
51+
await workmanager.initialize(callbackDispatcher);
52+
53+
try {
54+
await workmanager.registerOneOffTask(
55+
'test.oneoff.data',
56+
'dataTask',
57+
inputData: {
58+
'string': 'test',
59+
'number': 42,
60+
'boolean': true,
61+
'list': [1, 2, 3],
62+
'map': {'nested': 'value'},
63+
},
64+
);
65+
// Clean up
66+
await workmanager.cancelByUniqueName('test.oneoff.data');
67+
} on PlatformException catch (e) {
68+
if (Platform.isIOS && e.code.contains('bgTaskSchedulingFailed')) {
69+
// Expected on iOS in test environment
70+
} else {
71+
rethrow;
72+
}
5473
}
55-
}
56-
}, skip: !Platform.isIOS);
57-
58-
testWidgets('initialize & cancelByUniqueName - iOS',
59-
(WidgetTester tester) async {
60-
final wm = Workmanager();
61-
await wm.initialize(callbackDispatcher);
62-
try {
63-
await wm.registerOneOffTask(
64-
'dev.fluttercommunity.workmanagerExample.taskId',
65-
'taskName',
66-
);
67-
await wm.cancelByUniqueName(
68-
'dev.fluttercommunity.workmanagerExample.taskId',
69-
);
70-
} on PlatformException catch (e) {
71-
if (e.code !=
72-
'bgTaskSchedulingFailed(Error Domain=BGTaskSchedulerErrorDomain Code=1 "(null)") error') {
73-
rethrow;
74+
});
75+
76+
testWidgets('registerOneOffTask with all parameters (Android)', (WidgetTester tester) async {
77+
await workmanager.initialize(callbackDispatcher);
78+
79+
if (Platform.isAndroid) {
80+
await workmanager.registerOneOffTask(
81+
'test.oneoff.full',
82+
'fullTask',
83+
inputData: {'test': 'data'},
84+
initialDelay: const Duration(seconds: 1),
85+
constraints: Constraints(
86+
networkType: NetworkType.connected,
87+
requiresBatteryNotLow: true,
88+
requiresCharging: false,
89+
requiresDeviceIdle: false,
90+
requiresStorageNotLow: true,
91+
),
92+
existingWorkPolicy: ExistingWorkPolicy.replace,
93+
backoffPolicy: BackoffPolicy.exponential,
94+
backoffPolicyDelay: const Duration(seconds: 30),
95+
tag: 'test-tag',
96+
outOfQuotaPolicy: OutOfQuotaPolicy.dropWorkRequest,
97+
);
98+
// Clean up
99+
await workmanager.cancelByUniqueName('test.oneoff.full');
74100
}
75-
}
76-
}, skip: !Platform.isIOS);
77-
}
101+
});
102+
103+
testWidgets('registerPeriodicTask should work on supported platforms', (WidgetTester tester) async {
104+
await workmanager.initialize(callbackDispatcher);
105+
106+
try {
107+
await workmanager.registerPeriodicTask(
108+
'test.periodic.basic',
109+
'periodicTask',
110+
frequency: const Duration(minutes: 15),
111+
);
112+
// Clean up
113+
await workmanager.cancelByUniqueName('test.periodic.basic');
114+
} on PlatformException catch (e) {
115+
if (Platform.isIOS && e.code.contains('bgTaskSchedulingFailed')) {
116+
// Expected on iOS in test environment
117+
} else {
118+
rethrow;
119+
}
120+
}
121+
});
122+
123+
testWidgets('registerPeriodicTask with parameters (Android)', (WidgetTester tester) async {
124+
await workmanager.initialize(callbackDispatcher);
125+
126+
if (Platform.isAndroid) {
127+
await workmanager.registerPeriodicTask(
128+
'test.periodic.full',
129+
'periodicFullTask',
130+
frequency: const Duration(minutes: 15),
131+
flexInterval: const Duration(minutes: 5),
132+
inputData: {'periodic': 'data'},
133+
initialDelay: const Duration(seconds: 1),
134+
constraints: Constraints(
135+
networkType: NetworkType.unmetered,
136+
requiresBatteryNotLow: false,
137+
requiresCharging: true,
138+
),
139+
existingWorkPolicy: ExistingWorkPolicy.keep,
140+
backoffPolicy: BackoffPolicy.linear,
141+
backoffPolicyDelay: const Duration(seconds: 10),
142+
tag: 'periodic-tag',
143+
);
144+
// Clean up
145+
await workmanager.cancelByUniqueName('test.periodic.full');
146+
}
147+
});
148+
149+
testWidgets('registerProcessingTask should work on iOS only', (WidgetTester tester) async {
150+
await workmanager.initialize(callbackDispatcher);
151+
152+
if (Platform.isIOS) {
153+
try {
154+
await workmanager.registerProcessingTask(
155+
'test.processing',
156+
'processingTask',
157+
initialDelay: const Duration(seconds: 1),
158+
inputData: {'processing': 'data'},
159+
constraints: Constraints(
160+
networkType: NetworkType.connected,
161+
requiresCharging: true,
162+
),
163+
);
164+
// Clean up
165+
await workmanager.cancelByUniqueName('test.processing');
166+
} on PlatformException catch (e) {
167+
if (e.code.contains('bgTaskSchedulingFailed')) {
168+
// Expected in test environment
169+
} else {
170+
rethrow;
171+
}
172+
}
173+
} else {
174+
// Should throw UnsupportedError on Android
175+
expect(
176+
() => workmanager.registerProcessingTask(
177+
'test.processing',
178+
'processingTask',
179+
),
180+
throwsA(isA<UnsupportedError>()),
181+
);
182+
}
183+
});
184+
185+
testWidgets('cancelByUniqueName should succeed', (WidgetTester tester) async {
186+
await workmanager.initialize(callbackDispatcher);
187+
188+
// Should not throw even if task doesn't exist
189+
await workmanager.cancelByUniqueName('nonexistent.task');
190+
});
191+
192+
testWidgets('cancelByTag should work on Android only', (WidgetTester tester) async {
193+
await workmanager.initialize(callbackDispatcher);
194+
195+
if (Platform.isAndroid) {
196+
// Should not throw even if no tasks with tag exist
197+
await workmanager.cancelByTag('nonexistent-tag');
198+
} else {
199+
// Should throw UnsupportedError on iOS
200+
expect(
201+
() => workmanager.cancelByTag('test-tag'),
202+
throwsA(isA<UnsupportedError>()),
203+
);
204+
}
205+
});
206+
207+
testWidgets('cancelAll should succeed', (WidgetTester tester) async {
208+
await workmanager.initialize(callbackDispatcher);
209+
210+
try {
211+
await workmanager.cancelAll();
212+
} on PlatformException catch (e) {
213+
if (Platform.isIOS && e.code.contains('bgTaskSchedulingFailed')) {
214+
// Expected on iOS in some test environments
215+
} else {
216+
rethrow;
217+
}
218+
}
219+
});
220+
221+
testWidgets('isScheduled should work on Android only', (WidgetTester tester) async {
222+
await workmanager.initialize(callbackDispatcher);
223+
224+
if (Platform.isAndroid) {
225+
// Test with a task that doesn't exist
226+
final isScheduled = await workmanager.isScheduled('nonexistent.task');
227+
expect(isScheduled, false);
228+
229+
// Register a task and check if it's scheduled
230+
try {
231+
await workmanager.registerOneOffTask(
232+
'test.scheduled',
233+
'scheduledTask',
234+
);
235+
final isScheduledAfterRegister = await workmanager.isScheduled('test.scheduled');
236+
expect(isScheduledAfterRegister, true);
237+
238+
// Clean up
239+
await workmanager.cancelByUniqueName('test.scheduled');
240+
241+
// Check again after cancellation
242+
final isScheduledAfterCancel = await workmanager.isScheduled('test.scheduled');
243+
expect(isScheduledAfterCancel, false);
244+
} catch (e) {
245+
// Clean up even if test fails
246+
await workmanager.cancelByUniqueName('test.scheduled');
247+
rethrow;
248+
}
249+
} else {
250+
// Should throw UnsupportedError on iOS
251+
expect(
252+
() => workmanager.isScheduled('test-task'),
253+
throwsA(isA<UnsupportedError>()),
254+
);
255+
}
256+
});
257+
258+
testWidgets('printScheduledTasks should work on iOS only', (WidgetTester tester) async {
259+
await workmanager.initialize(callbackDispatcher);
260+
261+
if (Platform.isIOS) {
262+
final result = await workmanager.printScheduledTasks();
263+
expect(result, isA<String>());
264+
} else {
265+
// Should throw UnsupportedError on Android
266+
expect(
267+
() => workmanager.printScheduledTasks(),
268+
throwsA(isA<UnsupportedError>()),
269+
);
270+
}
271+
});
272+
273+
testWidgets('multiple task registration and cancellation flow', (WidgetTester tester) async {
274+
await workmanager.initialize(callbackDispatcher);
275+
276+
final taskIds = ['test.multi.1', 'test.multi.2', 'test.multi.3'];
277+
278+
try {
279+
// Register multiple tasks
280+
for (int i = 0; i < taskIds.length; i++) {
281+
await workmanager.registerOneOffTask(
282+
taskIds[i],
283+
'multiTask$i',
284+
inputData: {'index': i},
285+
);
286+
}
287+
288+
// Cancel individual tasks
289+
await workmanager.cancelByUniqueName(taskIds[0]);
290+
291+
if (Platform.isAndroid) {
292+
// Verify first task is cancelled, others remain
293+
expect(await workmanager.isScheduled(taskIds[0]), false);
294+
expect(await workmanager.isScheduled(taskIds[1]), true);
295+
expect(await workmanager.isScheduled(taskIds[2]), true);
296+
}
297+
298+
// Cancel all remaining tasks
299+
await workmanager.cancelAll();
300+
301+
if (Platform.isAndroid) {
302+
// Verify all tasks are cancelled
303+
for (final taskId in taskIds) {
304+
expect(await workmanager.isScheduled(taskId), false);
305+
}
306+
}
307+
} on PlatformException catch (e) {
308+
if (Platform.isIOS && e.code.contains('bgTaskSchedulingFailed')) {
309+
// Expected on iOS in test environment
310+
} else {
311+
rethrow;
312+
}
313+
} finally {
314+
// Ensure cleanup
315+
await workmanager.cancelAll();
316+
}
317+
});
318+
});
319+
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,16 @@ object Extractor {
372372
}
373373

374374
private fun extractPayload(call: MethodCall): String? {
375-
return call.argument<String>(REGISTER_TASK_PAYLOAD_KEY)
375+
val inputData = call.argument<Map<String, Any>>(REGISTER_TASK_PAYLOAD_KEY)
376+
return if (inputData != null) {
377+
// Convert Map to JSON string for storage in WorkManager Data
378+
try {
379+
org.json.JSONObject(inputData).toString()
380+
} catch (e: Exception) {
381+
null
382+
}
383+
} else {
384+
null
385+
}
376386
}
377387
}

0 commit comments

Comments
 (0)