flutter_any_download 1.1.5
flutter_any_download: ^1.1.5 copied to clipboard
Download any file and open Android and iOS

Flutter Any Download 📥 #
Easy-to-use download manager for Flutter with working progress notifications for both iOS and Android.
✨ Features #
- ✅ Simple API - Just 3 lines to download a file
- 📊 Progress Notifications - Built-in notification with download progress
- 🔔 iOS & Android Support - Works seamlessly on both platforms (Android & iOS notifications)
- 🎯 Callbacks - Track progress, completion, and errors
- 🚫 Cancellation - Cancel downloads anytime
- 🔕 Silent Downloads - Download without notifications
- ⚡ Multiple Downloads - Download multiple files simultaneously
- 🍎 iOS Foreground Notifications - Shows notifications even when app is open
- 📁 File Path Access - Get downloaded file path via callbacks
📦 Installation #
Add to your pubspec.yaml:
dependencies:
flutter_any_download: ^1.1.5
# Required dependencies
http: ^1.1.0
path_provider: ^2.1.0
flutter_local_notifications: ^17.0.0
permission_handler: ^11.0.0
Note: This version does not include tap-to-open functionality for a simpler, more focused implementation.
🚀 Quick Start #
1. Initialize (in main.dart) #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize once at app startup
await FlutterAnyDownload.instance.initialize();
// ✅ IMPORTANT FOR iOS: Request permission at startup
await FlutterAnyDownload.instance.requestPermission();
runApp(MyApp());
}
2. Download a File #
// Simple download with notification
final result = await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.pdf',
filename: 'document.pdf',
);
if (result.success) {
print('Downloaded to: ${result.filePath}');
// Use the file path to access the file
} else {
print('Error: ${result.message}');
}
That's it! 🎉
📖 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) {
// File downloaded successfully
print('File saved at: ${result.filePath}');
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Downloaded to: ${result.filePath}')),
);
}
},
child: 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;
print('Progress: ${(progress * 100).toInt()}%');
});
},
onComplete: (filePath) {
print('Download completed: $filePath');
// File is ready to use at filePath
},
onError: (error) {
print('Download failed: $error');
},
);
Silent Download (No Notifications) #
// Useful for downloading app data, configs, etc.
final result = await FlutterAnyDownload.instance.downloadSilent(
url: 'https://example.com/config.json',
filename: 'config.json',
saveToDownloads: false, // Save to app directory
);
if (result.success) {
// Use the downloaded file
final file = File(result.filePath!);
final contents = await file.readAsString();
print('Config: $contents');
}
Download Multiple Files #
Future<void> downloadMultipleFiles() async {
final urls = [
'https://example.com/file1.pdf',
'https://example.com/file2.pdf',
'https://example.com/file3.pdf',
];
for (int i = 0; i < urls.length; i++) {
final result = await FlutterAnyDownload.instance.download(
url: urls[i],
filename: 'file${i + 1}.pdf',
onComplete: (filePath) {
print('Downloaded ${i + 1}/3: $filePath');
},
);
}
}
Request Notification Permission #
// Check if notifications are enabled
bool enabled = await FlutterAnyDownload.instance.areNotificationsEnabled();
// Request permission (Android 13+ and iOS)
bool granted = await FlutterAnyDownload.instance.requestPermission();
if (!granted) {
// Show dialog explaining why permission is needed
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Permission Required'),
content: Text('Please enable notifications to see download progress.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
),
);
}
Cancel Downloads #
// Cancel all active downloads
await FlutterAnyDownload.instance.cancelAll();
Complete Widget Example with Progress Bar #
class DownloadWidget extends StatefulWidget {
@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),
SizedBox(height: 20),
Text('${(_progress * 100).toInt()}%'),
Text(_status),
if (_filePath != null) ...[
SizedBox(height: 10),
Text('Saved at:', style: TextStyle(fontWeight: FontWeight.bold)),
SelectableText(_filePath!, style: TextStyle(fontSize: 12)),
],
SizedBox(height: 20),
ElevatedButton(
onPressed: _isDownloading ? null : _download,
child: Text('Download'),
),
],
);
}
}
📱 Platform Configuration #
Android Configuration #
- Add permissions to
android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Internet permission -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Notification permission (Android 13+) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Storage permissions -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<application>
<!-- Your app configuration -->
</application>
</manifest>
- Update
android/app/build.gradle:
android {
compileSdkVersion 34
defaultConfig {
minSdkVersion 21
targetSdkVersion 34
}
}
iOS Configuration (IMPORTANT - Follow Exactly!) #
1. Update ios/Runner/Info.plist:
Add these keys to enable notifications and file access:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Your existing keys... -->
<!-- CRITICAL: Notification permissions -->
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>NSUserNotificationAlertStyle</key>
<string>alert</string>
<!-- File access permissions -->
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to save your downloaded files</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need permission to save downloaded files</string>
<!-- Document support -->
<key>UISupportsDocumentBrowser</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
</dict>
</plist>
2. Update ios/Runner/AppDelegate.swift:
Replace the entire file with this:
import UIKit
import Flutter
import UserNotifications
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// CRITICAL: Set notification center delegate
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
//CRITICAL: Handle foreground notifications
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. Update ios/Podfile:
Ensure minimum iOS version:
platform :ios, '12.0'
# Post install hook
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 (MANDATORY):
cd ios
rm -rf Pods Podfile.lock
cd ..
flutter clean
flutter pub get
cd ios
pod install
cd ..
flutter run
🎯 API Reference #
Main Methods #
initialize()
Initialize the download manager. Call once in main().
await FlutterAnyDownload.instance.initialize();
download()
Download a file with full control over options.
Future<DownloadResult> download({
required String url, // File URL
required String filename, // Save as filename
bool showNotification = true, // Show progress notification
bool saveToDownloads = true, // Save to Downloads folder (Android)
ProgressCallback? onProgress, // Progress callback
SuccessCallback? onComplete, // Success callback with file path
ErrorCallback? onError, // Error callback
})
downloadSilent()
Quick download without notifications.
Future<DownloadResult> downloadSilent({
required String url,
required String filename,
bool saveToDownloads = false,
})
requestPermission()
Request notification permission.
Future<bool> requestPermission()
areNotificationsEnabled()
Check if notifications are enabled.
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);
Download Result #
class DownloadResult {
final bool success; // Whether download succeeded
final String? filePath; // Path to downloaded file (null if failed)
final String message; // Human-readable message
}
📁 Accessing Downloaded Files #
Since this version focuses on notifications only (without tap-to-open), you can access downloaded files through:
Option 1: Via Callbacks #
await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.pdf',
filename: 'document.pdf',
onComplete: (filePath) {
print('File available at: $filePath');
// Use the file as needed
final file = File(filePath);
// Process file...
},
);
Option 2: 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...
}
Option 3: Implement Custom File Opening #
If you need to open files, you can add your own logic:
// Add to pubspec.yaml if needed:
// open_filex: ^4.3.0
import 'package:open_filex/open_filex.dart';
final result = await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.pdf',
filename: 'document.pdf',
onComplete: (filePath) async {
// Open the file with default app
await OpenFilex.open(filePath);
},
);
🔧 Advanced Usage #
Custom Save Location #
// Save to Downloads folder (Android only)
await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.pdf',
filename: 'document.pdf',
saveToDownloads: true, // Android: /storage/emulated/0/Download
// iOS: App Documents directory
);
// Save to app directory
await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.pdf',
filename: 'document.pdf',
saveToDownloads: false, // App's Documents directory
);
Error Handling #
try {
final result = await FlutterAnyDownload.instance.download(
url: 'https://example.com/file.pdf',
filename: 'document.pdf',
onError: (error) {
print('Download error: $error');
},
);
if (!result.success) {
// Handle download failure
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Download Failed'),
content: Text(result.message),
),
);
}
} catch (e) {
print('Exception: $e');
}
🐛 Troubleshooting #
Notifications Not Showing on Android #
- Check if permission is granted:
bool granted = await FlutterAnyDownload.instance.requestPermission();
- Ensure you're targeting Android 13+ in build.gradle:
targetSdkVersion 34
- Check device notification settings:
- Settings → Apps → Your App → Notifications → ON
Notifications Not Showing on iOS #
This is the most common issue! Follow these steps:
-
✅ Verify Info.plist has all required keys (see iOS Configuration above)
-
✅ Verify AppDelegate.swift has the notification delegate setup (see iOS Configuration above)
-
✅ Clean build after making changes:
cd ios && rm -rf Pods Podfile.lock && cd ..
flutter clean && flutter pub get
cd ios && pod install && cd ..
- ✅ Request permission explicitly in your code:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterAnyDownload.instance.initialize();
await FlutterAnyDownload.instance.requestPermission(); // ← CRITICAL!
runApp(MyApp());
}
-
✅ Test on REAL DEVICE (not simulator - notifications don't work properly on simulator)
-
✅ Check device settings:
- Settings → Your App → Notifications → Allow Notifications: ON
- Settings → Your App → Notifications → Sounds: ON
- Settings → Your App → Notifications → Badges: ON
-
✅ Check Do Not Disturb is OFF
-
✅ Check Xcode console for logs:
✅ FlutterAnyDownload initialized successfully
🔔 iOS notification permission: ✅ GRANTED
📊 Progress: 25%
✅ Completion notification shown
File Not Found After Download #
The file is saved to:
- Android (saveToDownloads: true):
/storage/emulated/0/Download/filename - Android (saveToDownloads: false): App's Documents directory
- iOS: Always app's Documents directory
Check the filePath in DownloadResult:
final result = await FlutterAnyDownload.instance.download(...);
print('File saved at: ${result.filePath}');
📝 Best Practices #
- Initialize Once: Call
initialize()only once inmain() - Request Permissions Early: Request notification permission at app startup (especially for iOS)
- Handle Errors: Always check
result.successand handle failures - Show Progress: Use
onProgresscallback for better UX on large files - Cancel When Needed: Call
cancelAll()when user navigates away - Test on Real Devices: Notifications behave differently on simulators (especially iOS)
- iOS Clean Builds: After changing Info.plist or AppDelegate.swift, always do a clean build
- Store File Paths: Save file paths from callbacks for later use
iOS Notification Features #
What's Fixed in This Version: #
- ✅ Foreground Notifications: Notifications now show even when app is open
- ✅ Progress Updates: Shows download progress every 5% (0% → 5% → 10%...)
- ✅ Completion Notification: Shows success notification with sound
- ✅ Error Notifications: Shows failure notification if download fails
- ✅ Proper Permission Flow: Uses iOS native API correctly
- ✅ Unique Notification IDs: No notification collision
- ✅ Time-Sensitive Priority: High priority for important notifications
- ✅ Sound & Badge Support: Plays sound and shows badge count
iOS Notification Behavior: #
- Progress Notifications: Updates every 5% (reduces spam)
- Completion Notification: Shows with sound and high priority
- Badge Count: Shows download percentage as badge
- Foreground Display: Shows notifications even when app is in foreground
- No Tap Action: Notifications are informational only (file path provided via callbacks)
🚨 Common Mistakes to Avoid #
Wrong - Forgetting Permission Request #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterAnyDownload.instance.initialize();
// Missing requestPermission() call!
runApp(MyApp());
}
Correct - Request Permission #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterAnyDownload.instance.initialize();
await FlutterAnyDownload.instance.requestPermission(); // ← Add this!
runApp(MyApp());
}
Wrong - Not Storing File Path #
await FlutterAnyDownload.instance.download(
url: url,
filename: filename,
);
// How do I access the file now? 🤔
Correct - Store File Path #
String? downloadedFilePath;
final result = await FlutterAnyDownload.instance.download(
url: url,
filename: filename,
onComplete: (filePath) {
downloadedFilePath = filePath;
// Now you can use it!
},
);
// Or use result.filePath
if (result.success) {
downloadedFilePath = result.filePath;
}
Wrong - Testing on iOS Simulator #
iOS Simulator me notifications properly work nahi karte
Correct - Test on Real Device #
Always test on real iOS device for notifications
Wrong - Not Doing Clean Build #
Info.plist or AppDelegate change karne ke baad clean build nahi kiya
Correct - Always Clean Build #
cd ios && rm -rf Pods Podfile.lock && cd ..
flutter clean && flutter pub get
cd ios && pod install && cd ..
What's Included: #
- Download progress notifications
- Download completion notifications
- Error notifications
- File path access via callbacks and
DownloadResult - iOS & Android support
- All core download functionality
🙏 Credits #
Built with:
- http - HTTP client
- flutter_local_notifications - Local notifications
- path_provider - File paths
- permission_handler - Permission management
If this package helped you, please give it a ⭐ on GitHub! #
Made with ❤️ for Flutter developers
Simple, Clean, and Focused on Downloads + Notifications!