downloadWithProgress static method
Downloads a file with smart retry logic and HTTP-aware error handling
url - File URL (any server)
targetPath - Local file path to save to
token - Optional authorization token (e.g., HuggingFace, custom auth)
maxRetries - Maximum number of retry attempts for transient errors (default: 10)
cancelToken - Optional token for cancellation
foreground - Android foreground service mode:
- null (default): auto-detect based on file size (>500MB = foreground)
- true: always use foreground (shows notification)
- false: never use foreground
Note: Auth errors (401/403/404) fail after 1 attempt, regardless of maxRetries. Only network errors and server errors (5xx) will be retried up to maxRetries times. Returns a stream of progress percentages (0-100)
The stream will emit DownloadCancelledException if cancelled via cancelToken.
Implementation
static Stream<int> downloadWithProgress({
required String url,
required String targetPath,
String? token,
int maxRetries = 10,
CancelToken? cancelToken,
bool? foreground,
}) {
final progress = StreamController<int>();
StreamSubscription? currentListener;
StreamSubscription? cancellationListener;
String? currentTaskId; // ← ADD: Store task ID for cancellation
// Listen for cancellation
if (cancelToken != null) {
cancellationListener =
cancelToken.whenCancelled.asStream().listen((_) async {
debugPrint('🚫 Cancellation requested');
// Cancel the actual download task
if (currentTaskId != null) {
debugPrint('🚫 Cancelling task: $currentTaskId');
try {
await FileDownloader().cancelTaskWithId(
currentTaskId!); // ← ADD: Actually cancel the task
} catch (e) {
debugPrint('⚠️ Failed to cancel task: $e');
}
}
if (!progress.isClosed) {
progress.addError(
DownloadCancelledException(
cancelToken.cancelReason ?? 'Download cancelled',
StackTrace.current,
),
);
progress.close();
}
currentListener?.cancel();
cancellationListener?.cancel();
});
}
// Configure FileDownloader and start download
_ensureConfigured(foreground).then((_) async {
await _downloadWithSmartRetry(
url: url,
targetPath: targetPath,
token: token,
maxRetries: maxRetries,
progress: progress,
currentAttempt: 1,
currentListener: currentListener,
cancelToken: cancelToken,
onListenerCreated: (listener) {
currentListener = listener;
},
onTaskCreated: (taskId) {
currentTaskId = taskId;
},
);
}).whenComplete(() {
// Clean up cancellation listener when download completes
cancellationListener?.cancel();
});
return progress.stream;
}