flutter_any_download 1.1.9
flutter_any_download: ^1.1.9 copied to clipboard
Simple Flutter download manager for Android & iOS with progress notifications, callbacks, cancellation, silent mode, and multiple file downloads.

flutter_any_download #
A simple, production-ready download manager for Flutter with real-time progress notifications, cancellation support, and seamless iOS & Android compatibility.
Screenshots #
Home Screen Progress Download Multiple Downloads
Demo #
Features #
- Simple API — Download any file in 3 lines of code
- Progress Notifications — Built-in notification bar with live download progress
- iOS & Android Support — Fully functional notifications on both platforms
- Progress Callbacks — Track download progress, completion, and errors in your UI
- Cancellation — Cancel individual or all active downloads at any time
- Silent Downloads — Download files without showing any notifications
- Multiple File Downloads — Download several files sequentially or in batches
- iOS Foreground Notifications — Notifications display even when the app is in the foreground
- File Path Access — Get the saved file path via callbacks or
DownloadResult - Android Scoped Storage — Handles Android 9 / 10 / 11+ storage correctly
Installation #
Add to your pubspec.yaml:
dependencies:
flutter_any_download: ^1.1.9
This package bundles all required dependencies internally. No additional packages are needed.
Quick Start #
1. Initialize (call once in main.dart) #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterAnyDownload.instance.initialize();
// Required for iOS — request notification permission at startup
await FlutterAnyDownload.instance.requestPermission();
runApp(MyApp());
}
2. Download a File #
final result = await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.pdf',
filename: 'document.pdf',
);
if (result.success) {
print('Saved to: ${result.filePath}');
} else {
print('Error: ${result.message}');
}
Usage Examples #
Basic Download #
ElevatedButton(
onPressed: () async {
final result = await FlutterAnyDownload.instance.download(
url: 'https://example.com/sample.pdf',
filename: 'my_file.pdf',
);
if (result.success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Downloaded to: ${result.filePath}')),
);
}
},
child: const Text('Download File'),
);
Download with Progress Callback #
double progress = 0.0;
await FlutterAnyDownload.instance.download(
url: 'https://example.com/large_file.zip',
filename: 'archive.zip',
onProgress: (downloaded, total) {
setState(() {
progress = downloaded / total;
});
},
onComplete: (filePath) {
print('Download complete: $filePath');
},
onError: (error) {
print('Download failed: $error');
},
);
Silent Download (No Notifications) #
final result = await FlutterAnyDownload.instance.downloadSilent(
url: 'https://example.com/config.json',
filename: 'config.json',
saveToDownloads: false,
);
if (result.success) {
final file = File(result.filePath!);
final contents = await file.readAsString();
print('Config loaded: $contents');
}
Download Multiple Files #
Future<void> downloadMultiple() async {
final files = [
{'url': 'https://example.com/file1.pdf', 'name': 'file1.pdf'},
{'url': 'https://example.com/file2.pdf', 'name': 'file2.pdf'},
{'url': 'https://example.com/file3.pdf', 'name': 'file3.pdf'},
];
for (int i = 0; i < files.length; i++) {
final result = await FlutterAnyDownload.instance.download(
url: files[i]['url']!,
filename: files[i]['name']!,
onComplete: (filePath) {
print('Downloaded ${i + 1}/${files.length}: $filePath');
},
);
}
}
Request & Check Notification Permission #
// Check current status
bool enabled = await FlutterAnyDownload.instance.areNotificationsEnabled();
// Request permission (Android 13+ and iOS)
bool granted = await FlutterAnyDownload.instance.requestPermission();
if (!granted) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Permission Required'),
content: const Text(
'Enable notifications to see download progress.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
Cancel All Downloads #
await FlutterAnyDownload.instance.cancelAll();
Full Widget with Progress Bar #
class DownloadWidget extends StatefulWidget {
const DownloadWidget({super.key});
@override
State<DownloadWidget> createState() => _DownloadWidgetState();
}
class _DownloadWidgetState extends State<DownloadWidget> {
double _progress = 0.0;
bool _isDownloading = false;
String _status = 'Ready';
String? _filePath;
Future<void> _download() async {
setState(() {
_isDownloading = true;
_status = 'Downloading...';
_filePath = null;
});
final result = await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.zip',
filename: 'download.zip',
onProgress: (downloaded, total) {
setState(() {
_progress = downloaded / total;
_status =
'${(downloaded / 1024 / 1024).toStringAsFixed(2)} MB / '
'${(total / 1024 / 1024).toStringAsFixed(2)} MB';
});
},
onComplete: (filePath) {
setState(() => _filePath = filePath);
},
);
setState(() {
_isDownloading = false;
_status = result.success ? 'Complete ✅' : 'Failed ❌';
if (result.success) _filePath = result.filePath;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
LinearProgressIndicator(value: _progress),
const SizedBox(height: 20),
Text('${(_progress * 100).toInt()}%'),
Text(_status),
if (_filePath != null) ...[
const SizedBox(height: 10),
const Text('Saved at:', style: TextStyle(fontWeight: FontWeight.bold)),
SelectableText(_filePath!, style: const TextStyle(fontSize: 12)),
],
const SizedBox(height: 20),
ElevatedButton(
onPressed: _isDownloading ? null : _download,
child: const Text('Download'),
),
],
);
}
}
Platform Configuration #
Android #
Add permissions to android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Internet -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Notifications (Android 13+) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Storage (Android 9 and below only) -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application>
<!-- your app configuration -->
</application>
</manifest>
Update android/app/build.gradle:
android {
compileSdk 35
defaultConfig {
minSdkVersion 21
targetSdkVersion 35
}
}
iOS #
1. ios/Runner/Info.plist
<!-- Background modes for notifications -->
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>NSUserNotificationAlertStyle</key>
<string>alert</string>
<!-- File sharing -->
<key>UISupportsDocumentBrowser</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
2. ios/Runner/AppDelegate.swift
import UIKit
import Flutter
import UserNotifications
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// Show notifications while app is in foreground
override func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
if #available(iOS 14.0, *) {
completionHandler([.banner, .sound, .badge])
} else {
completionHandler([.alert, .sound, .badge])
}
}
}
3. ios/Podfile
platform :ios, '12.0'
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
end
end
end
4. Clean Build (required after iOS config changes)
cd ios && rm -rf Pods Podfile.lock && cd ..
flutter clean && flutter pub get
cd ios && pod install && cd ..
flutter run
API Reference #
initialize() #
Initialize the download manager. Call once in main() before runApp.
await FlutterAnyDownload.instance.initialize();
download() #
Download a file with full control.
Future<DownloadResult> download({
required String url,
required String filename,
bool showNotification = true,
bool saveToDownloads = true,
ProgressCallback? onProgress,
SuccessCallback? onComplete,
ErrorCallback? onError,
})
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
String |
required | URL of the file to download |
filename |
String |
required | Name to save the file as |
showNotification |
bool |
true |
Show progress notification |
saveToDownloads |
bool |
true |
Save to Downloads folder (Android) |
onProgress |
ProgressCallback? |
null |
Called with (downloaded, total) bytes |
onComplete |
SuccessCallback? |
null |
Called with the saved file path |
onError |
ErrorCallback? |
null |
Called with error message |
downloadSilent() #
Download without notifications.
Future<DownloadResult> downloadSilent({
required String url,
required String filename,
bool saveToDownloads = false,
})
requestPermission() #
Request notification permission (required for iOS; Android 13+).
Future<bool> requestPermission()
areNotificationsEnabled() #
Check if notification permission is granted.
Future<bool> areNotificationsEnabled()
cancelAll() #
Cancel all active downloads.
Future<void> cancelAll()
Callbacks #
typedef ProgressCallback = void Function(int downloaded, int total);
typedef SuccessCallback = void Function(String filePath);
typedef ErrorCallback = void Function(String error);
DownloadResult #
class DownloadResult {
final bool success; // true if download succeeded
final String? filePath; // absolute path to file (null on failure)
final String message; // human-readable status message
}
File Save Locations #
| Platform | saveToDownloads: true |
saveToDownloads: false |
|---|---|---|
| Android 10+ | App-specific external storage | App documents directory |
| Android 9 and below | /storage/emulated/0/Download/ |
App documents directory |
| iOS | App documents directory | App documents directory |
Access the exact path via result.filePath or the onComplete callback.
Accessing Downloaded Files #
Via DownloadResult #
final result = await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.pdf',
filename: 'document.pdf',
);
if (result.success && result.filePath != null) {
final file = File(result.filePath!);
// use file
}
Via onComplete Callback #
await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.pdf',
filename: 'document.pdf',
onComplete: (filePath) {
final file = File(filePath);
// use file
},
);
Opening Files (optional, add separately) #
# pubspec.yaml
dependencies:
open_filex: ^4.3.0
import 'package:open_filex/open_filex.dart';
await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.pdf',
filename: 'document.pdf',
onComplete: (filePath) async {
await OpenFilex.open(filePath);
},
);
Troubleshooting #
Notifications not showing on Android #
- Confirm permission is granted:
await FlutterAnyDownload.instance.requestPermission(); - Check
targetSdkVersionis 33 or higher inbuild.gradle. - On the device: Settings → Apps → Your App → Notifications → ON.
Notifications not showing on iOS #
- Verify
Info.plisthas all required keys (see iOS Configuration above). - Verify
AppDelegate.swiftsetsUNUserNotificationCenter.current().delegate = self. - Run a clean build after any iOS config change.
- Call
requestPermission()at app startup — this is mandatory. - Test on a real device — notifications do not work in the iOS Simulator.
- On the device: Settings → Your App → Notifications → Allow Notifications: ON.
- Check Xcode console for diagnostic logs:
✅ FlutterAnyDownload initialized successfully 🔔 iOS notification permission: ✅ GRANTED 📊 Progress: 25% ✅ Completion notification shown
File not found after download #
Check the path from result.filePath:
final result = await FlutterAnyDownload.instance.download(...);
print('Saved at: ${result.filePath}');
Best Practices #
- Call
initialize()once inmain()beforerunApp. - Call
requestPermission()early at app startup, especially on iOS. - Always check
result.successand handle failures gracefully. - Use
onProgressto display progress indicators for large files. - Call
cancelAll()when navigating away from a download screen. - Store file paths returned from callbacks for later use.
- Always test notifications on a real device (not a simulator).
- Do a clean build after modifying
Info.plistorAppDelegate.swifton iOS.
Common Mistakes #
Forgetting to request permission:
// Wrong
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterAnyDownload.instance.initialize();
runApp(MyApp()); // ← missing requestPermission()
}
// Correct
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterAnyDownload.instance.initialize();
await FlutterAnyDownload.instance.requestPermission(); // ← required
runApp(MyApp());
}
Not storing the file path:
// Wrong
await FlutterAnyDownload.instance.download(url: url, filename: filename);
// How do I access the file now?
// Correct
final result = await FlutterAnyDownload.instance.download(
url: url,
filename: filename,
);
if (result.success) {
final filePath = result.filePath; // use this
}
If this package helped you, please leave a ⭐ on GitHub.
Made with ❤️ for the Flutter community