Line data Source code
1 : import 'dart:async';
2 : import 'package:flutter/foundation.dart';
3 : import 'native_work_manager.dart';
4 : import 'events.dart';
5 :
6 : /// A controller for a specific background task.
7 : ///
8 : /// Returned by [NativeWorkManager.enqueue] to allow tracking progress and
9 : /// completion of a specific task without manually filtering global streams.
10 : ///
11 : /// ## Usage
12 : ///
13 : /// ```dart
14 : /// final handler = await NativeWorkManager.enqueue(
15 : /// taskId: 'download-video',
16 : /// worker: NativeWorker.httpDownload(url: '...'),
17 : /// );
18 : ///
19 : /// // 1. Check if OS accepted the task
20 : /// if (handler.scheduleResult != ScheduleResult.accepted) {
21 : /// print('Failed to schedule: ${handler.scheduleResult}');
22 : /// return;
23 : /// }
24 : ///
25 : /// // 2. Listen to progress for THIS task only
26 : /// handler.progress.listen((p) {
27 : /// print('Progress: ${p.progress}% (${p.networkSpeedHuman})');
28 : /// });
29 : ///
30 : /// // 3. Wait for final result
31 : /// final event = await handler.result;
32 : /// if (event.success) {
33 : /// print('Finished! Result: ${event.resultData}');
34 : /// }
35 : /// ```
36 : @immutable
37 : class TaskHandler {
38 : /// The unique ID of the task.
39 : final String taskId;
40 :
41 : /// The result of the scheduling request.
42 : ///
43 : /// If [ScheduleResult.accepted], the task was successfully added to the
44 : /// OS queue. Otherwise, the task will not run.
45 : final ScheduleResult scheduleResult;
46 :
47 6 : const TaskHandler({
48 : required this.taskId,
49 : required this.scheduleResult,
50 : });
51 :
52 : /// A stream of progress updates for this specific task.
53 : ///
54 : /// Only emits updates if the worker supports progress reporting (e.g.
55 : /// httpDownload, httpUpload).
56 0 : Stream<TaskProgress> get progress =>
57 0 : NativeWorkManager.progress.where((p) => p.taskId == taskId);
58 :
59 : /// A stream of lifecycle events for this specific task.
60 : ///
61 : /// Emits when the task starts, succeeds, or fails.
62 0 : Stream<TaskEvent> get events =>
63 0 : NativeWorkManager.events.where((e) => e.taskId == taskId);
64 :
65 : /// A future that completes when the task finishes (either success or failure).
66 : ///
67 : /// This is a convenience for `events.firstWhere((e) => !e.isStarted)`.
68 : ///
69 : /// **Note:** If the app is terminated and restarted, this future will never
70 : /// complete because the stream is transient. For long-running tasks, always
71 : /// use [NativeWorkManager.getTaskStatus] or [NativeWorkManager.events]
72 : /// subscription for robust state management.
73 0 : Future<TaskEvent> get result =>
74 0 : events.firstWhere((e) => !e.isStarted).timeout(
75 : const Duration(days: 7), // Long timeout for background tasks
76 0 : onTimeout: () => throw TimeoutException(
77 0 : 'Task $taskId did not complete within 7 days or was lost.',
78 : ),
79 : );
80 :
81 : /// Cancel this task.
82 0 : Future<void> cancel() => NativeWorkManager.cancel(taskId: taskId);
83 :
84 : /// Get the current status of this task.
85 0 : Future<TaskStatus?> getStatus() =>
86 0 : NativeWorkManager.getTaskStatus(taskId: taskId);
87 : }
88 :
89 : /// Helper extensions for displaying [TaskProgress] information.
90 : extension TaskProgressExtensions on TaskProgress {
91 : /// Returns the network speed in a human-readable format (e.g., "1.2 MB/s").
92 1 : String get networkSpeedHuman {
93 1 : if (networkSpeed == null) return 'n/a';
94 5 : if (networkSpeed! < 1024) return '${networkSpeed!.toStringAsFixed(1)} B/s';
95 3 : if (networkSpeed! < 1024 * 1024) {
96 4 : return '${(networkSpeed! / 1024).toStringAsFixed(1)} KB/s';
97 : }
98 5 : return '${(networkSpeed! / (1024 * 1024)).toStringAsFixed(1)} MB/s';
99 : }
100 :
101 : /// Returns the estimated time remaining in a human-readable format (e.g., "2m 15s").
102 1 : String get timeRemainingHuman {
103 1 : if (timeRemaining == null) return 'unknown';
104 6 : if (timeRemaining!.inSeconds < 60) return '${timeRemaining!.inSeconds}s';
105 3 : if (timeRemaining!.inMinutes < 60) {
106 3 : final s = timeRemaining!.inSeconds % 60;
107 3 : return '${timeRemaining!.inMinutes}m ${s}s';
108 : }
109 3 : final m = timeRemaining!.inMinutes % 60;
110 3 : return '${timeRemaining!.inHours}h ${m}m';
111 : }
112 : }
|