downloadFile method
Future<String?>
downloadFile({
- required String downloadFileUrl,
- void onProgress(
- double progress
- @Deprecated('Use a try-catch block to handle FileDownloadException instead.') void onError(
- String error
- void onTimeLeft(
- String error
- void onSpeed(
- String error
- void onTotalSize(
- String error
- void onDownloadedSize(
- String error
- String? downloadFileName,
- bool deleteOnError = false,
Downloads a file from the provided URL to the device's external storage.
Prevents concurrent downloads by throwing a FileDownloadException with DownloadErrorType.alreadyRunning if a download is currently active.
The file is saved in the external storage directory with the name
specified by downloadFileName (defaults to "downloadApk.apk").
Parameters:
downloadFileUrl: The direct URL to the APK file.onProgress: Callback returning the download progress as a double (0.0 to 1.0).onError: Callback for legacy error handling.onTimeLeft: Callback returning a formatted string of the estimated time remaining.onSpeed: Callback returning a formatted string of the current download speed.onTotalSize: Callback returning a formatted string of the total file size.onDownloadedSize: Callback returning a formatted string of the bytes downloaded so far.downloadFileName: Optional custom name for the downloaded file (without the .apk extension).deleteOnError: If true, automatically deletes the partial file if the download fails due to a network or server error. Defaults to false.
Returns the absolute path to the downloaded file on success, or null if handled via legacy error.
Implementation
Future<String?> downloadFile({
required String downloadFileUrl,
void Function(double progress)? onProgress,
@Deprecated(
'Use a try-catch block to handle FileDownloadException instead.',
)
void Function(String error)? onError,
void Function(String timeLeft)? onTimeLeft,
void Function(String speed)? onSpeed,
void Function(String totalSize)? onTotalSize,
void Function(String downloadedSize)? onDownloadedSize,
String? downloadFileName,
bool deleteOnError = false,
}) async {
if (_isDownloading) {
final exception = FileDownloadException(
type: DownloadErrorType.alreadyRunning,
originalError:
"A download task is currently active. Please wait for it to finish or cancel it.",
);
return _handleErrorBridge(
exception,
"Download already in progress",
onError,
);
}
_isDownloading = true;
_cancelToken = CancelToken();
int lastBytes = 0;
DateTime lastUpdateTime = DateTime.now();
try {
Directory? directory = await getExternalStorageDirectory();
if (directory == null) {
final exception = FileDownloadException(
type: DownloadErrorType.storageAccessDenied,
originalError: "Unable to access storage directory",
);
return _handleErrorBridge(
exception,
"Unable to access storage directory",
onError,
);
}
String savePath =
"${directory.path}/${downloadFileName ?? "downloadApk"}.apk";
_downloadPath = savePath;
Response response = await _dio.download(
downloadFileUrl,
savePath,
cancelToken: _cancelToken,
onReceiveProgress: (count, total) {
if (total > 0) {
if (onProgress != null) {
onProgress(count / total);
}
if (onTotalSize != null) {
onTotalSize(_formatBytes(total));
}
if (onDownloadedSize != null) {
onDownloadedSize(_formatBytes(count));
}
DateTime now = DateTime.now();
Duration interval = now.difference(lastUpdateTime);
if (interval.inMilliseconds >= 500 || count == total) {
int bytesSinceLast = count - lastBytes;
double secondsSinceLast = interval.inMilliseconds / 1000.0;
if (secondsSinceLast > 0) {
double speedBps = bytesSinceLast / secondsSinceLast;
if (onSpeed != null) {
onSpeed(_formatSpeed(speedBps));
}
if (onTimeLeft != null && speedBps > 0) {
double remainingBytes = (total - count).toDouble();
double remainingSeconds = remainingBytes / speedBps;
onTimeLeft(_formatTimeLeft(remainingSeconds));
}
}
lastBytes = count;
lastUpdateTime = now;
}
}
},
);
if (response.statusCode == 200) {
_downloadPath = null;
return savePath;
} else {
// Add the cleanup here too!
if (deleteOnError && _downloadPath != null) {
File file = File(_downloadPath!);
if (await file.exists()) {
try {
await file.delete();
log(
"Partial file deleted automatically due to bad response code.",
);
} catch (_) {}
}
_downloadPath = null;
}
final exception = FileDownloadException(
type: DownloadErrorType.badResponse,
statusCode: response.statusCode,
originalError: "Server returned status: ${response.statusCode}",
);
return _handleErrorBridge(
exception,
"Unable to download the file",
onError,
);
}
} on DioException catch (e, sc) {
log("DioException in downloadFile", error: e, stackTrace: sc);
if (deleteOnError &&
e.type != DioExceptionType.cancel &&
_downloadPath != null) {
File file = File(_downloadPath!);
if (await file.exists()) {
try {
await file.delete();
log("Partial file deleted automatically due to DioException.");
} catch (_) {}
}
_downloadPath = null; // Clear state after deleting
}
final exception = FileDownloadException(
type: _mapDioErrorToEnum(e.type),
statusCode: e.response?.statusCode,
originalError: e,
);
String legacyErrorString = _getLegacyErrorMessage(e);
return _handleErrorBridge(exception, legacyErrorString, onError);
} catch (e, sc) {
log("Unknown Exception in downloadFile", error: e, stackTrace: sc);
// 3. Add Auto-Cleanup for unknown errors too
if (deleteOnError && _downloadPath != null) {
File file = File(_downloadPath!);
if (await file.exists()) {
try {
await file.delete();
log("Partial file deleted automatically due to Unknown Exception.");
} catch (_) {}
}
_downloadPath = null; // Clear state after deleting
}
final exception = FileDownloadException(
type: DownloadErrorType.unknown,
originalError: e,
);
return _handleErrorBridge(
exception,
"Error Occurred while downloading the file",
onError,
);
} finally {
_isDownloading = false;
}
}