file_saver_ffi 0.2.0
file_saver_ffi: ^0.2.0 copied to clipboard
A high-performance file saver for Flutter using FFI and JNI. Effortlessly save to gallery (images/videos) or device storage with original quality and custom album support.
File Saver FFI #
A high-performance file saver for Flutter using FFI and JNI. Effortlessly save to gallery (images/videos) or device storage with original quality and custom album support.
Features #
- 🖼️ Gallery Saving – Save images and videos to iOS Photos or Android Gallery with custom albums
- ⚡ Native Performance – Powered by FFI (iOS) and JNI (Android) for near-zero latency
- 📁 Universal Storage – Save any file type (PDF, ZIP, DOCX, etc.) to device storage
- 💾 Original Quality – Files saved bit-for-bit without compression or metadata loss
- 📊 Progress & Cancellation – Real-time progress tracking with cancellable operations
- ⚙️ Conflict Resolution – Auto-rename, overwrite, skip, or fail on existing files
If you want to say thank you, star us on GitHub or like us on pub.dev.
🤖 Ask AI About This Library #
Have questions about file_saver_ffi? Get instant AI-powered answers about the library's features, usage, and best
practices.
→ Chat with AI Documentation Assistant
Ask anything like:
- "How do I save a video to the gallery with progress tracking?"
- "What's the difference between saveBytes and saveFile?"
- "How to handle permission errors on Android 10+?"
- "Show me examples of custom file types"
Installation #
First, follow the package installation instructions and add
file_saver_ffi to your app.
Quick Start #
Platform Setup #
Android Configuration
Add to android/app/src/main/AndroidManifest.xml:
<!-- Only required for Android 9 (API 28) and below -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
Note: Android 10+ uses scoped storage automatically and does not require this permission.
Supported: API 21+ (Android 5.0+)
iOS Configuration
Add to ios/Runner/Info.plist:
<!-- For Photos Library Access (images/videos) -->
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app needs permission to save photos and videos to your library</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs permission to access your photo library</string>
<!-- Prevent automatic "Select More Photos" prompt on iOS 14+ -->
<key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
<true/>
<!-- Optional: Make files visible in Files app -->
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
Supported: iOS 13.0+
Basic Usage #
import 'package:file_saver_ffi/file_saver_ffi.dart';
try {
// Save image bytes
final uri = await FileSaver.instance.saveAsync(
fileBytes: SaveBytesInput(imageBytes),
fileName: 'my_image',
fileType: ImageType.jpg,
);
print('Saved to: $uri');
} on PermissionDeniedException catch (e) {
print('Permission denied: ${e.message}');
} on FileSaverException catch (e) {
print('Save failed: ${e.message}');
}
Core Concepts #
Unified API #
The library provides a single, consistent API for all save operations using SaveInput polymorphism:
save(input: ...): Stream-based API with full control (progress, cancellation).saveAsync(input: ...): Future-based API for simple usage.
Input Sources (SaveInput)
Use the appropriate input class for your data source:
| Input Type | Data Source | Use Case |
|---|---|---|
SaveBytesInput |
Uint8List |
Small files in memory (images, generated PDFs) |
SaveFileInput |
String (path) |
Large files from disk (videos, recordings) |
SaveNetworkInput |
String (URL) |
Download and save directly from internet |
API Matrix
| Input Source | Stream API | Async API | Interactive 🔮 |
|---|---|---|---|
| Bytes | save(SaveBytesInput) |
saveAsync(SaveBytesInput) |
saveAs() |
| File | save(SaveFileInput) |
saveAsync(SaveFileInput) |
saveAs() |
| Network | save(SaveNetworkInput) |
saveAsync(SaveNetworkInput) |
saveAs() |
File Types #
The library supports 35+ file formats across 4 categories:
| Category | Formats | Count | Example Types |
|---|---|---|---|
| Images | PNG, JPG, GIF, WebP, BMP, HEIC, HEIF, TIFF, ICO, DNG | 12 | ImageType.png, ImageType.jpg |
| Videos | MP4, MOV, MKV, WebM, AVI, 3GP, M4V, FLV, WMV, HEVC | 12 | VideoType.mp4, VideoType.mov |
| Audio | MP3, AAC, WAV, FLAC, OGG, M4A, AMR, Opus, AIFF, CAF | 11 | AudioType.mp3, AudioType.aac |
| Custom | Any format via extension + MIME type | ∞ | CustomFileType(ext: 'pdf', mimeType: 'application/pdf') |
Save Locations #
Control where files are saved using platform-specific enum values:
Android (AndroidSaveLocation)
| Value | Directory | Use Case |
|---|---|---|
.downloads |
Downloads/ |
General files (default) |
.pictures |
Pictures/ |
Images |
.movies |
Movies/ |
Videos |
.music |
Music/ |
Audio |
.dcim |
DCIM/ |
Camera media |
iOS (IosSaveLocation)
| Value | Destination | Use Case |
|---|---|---|
.documents |
Documents Dir | General files (default) |
.photos |
Photos Library | Images/Videos only |
Conflict Resolution #
Handle existing files with 4 strategies:
| Strategy | Behavior | Use Case |
|---|---|---|
autoRename (default) |
Appends (1), (2), etc. to filename | Safe, prevents data loss |
overwrite |
Replaces existing file* | Update existing files |
fail |
Throws FileExistsException |
Strict validation |
skip |
Returns existing file URI | Idempotent saves |
* Platform limitations:
- iOS Photos: Can only overwrite files owned by your app
- Android 10+: Can only overwrite files owned by your app (scoped storage)
Common Use Cases #
Download video from Network to Gallery #
final uri = await FileSaver.instance.saveAsync(
input: SaveNetworkInput(
url: 'https://example.com/video.mp4',
headers: {'Authorization': 'Bearer token'}, // Optional headers
timeout: Duration(minutes: 5), // Custom timeout
),
fileName: 'downloaded_video',
fileType: VideoType.mp4,
saveLocation: Platform.isAndroid
? AndroidSaveLocation.movies
: IosSaveLocation.photos,
);
Progress Tracking #
// Using Unified API
await FileSaver.instance.saveAsync(
input: SaveNetworkInput(url: '...'),
fileName: 'video',
fileType: VideoType.mp4,
onProgress: (progress) {
print('Download progress: ${(progress * 100).toInt()}%');
},
);
Cancellation #
// Stream API allows cancellation
final subscription = FileSaver.instance.save(
input: SaveNetworkInput(url: '...'), // Works for all inputs
fileName: 'video',
fileType: VideoType.mp4,
).listen((event) {
if (event is SaveProgressCancelled) {
print('Cancelled!');
}
});
// Cancel anytime
subscription.cancel();
API Reference #
Unified API (Recommended) #
saveAsync
Future-based API for simple usage.
Future<Uri> saveAsync({
required SaveInput input,
required String fileName,
required FileType fileType,
SaveLocation? saveLocation,
String? subDir,
ConflictResolution conflictResolution,
Function(double)? onProgress,
})
save
Stream-based API for advanced control (cancellation, detailed events).
Stream<SaveProgress> save({
required SaveInput input,
required String fileName,
required FileType fileType,
// ... same optional params
})
Input Models #
SaveBytesInput
fileBytes:Uint8List(Required)
SaveFileInput
filePath:String(Required - absolute path)
SaveNetworkInput
url:String(Required)headers:Map<String, String>?(Optional)timeout:Duration(Default: 60s)
Direct API #
Specific methods are still available but save/saveAsync is recommended.
saveBytes/saveBytesAsyncsaveFile/saveFileAsyncsaveNetwork/saveNetworkAsync
SaveProgress Events #
Stream API emits these sealed class events:
| Event | Properties | Description |
|---|---|---|
SaveProgressStarted |
- | Operation began |
SaveProgressUpdate |
progress: double |
Progress from 0.0 to 1.0 |
SaveProgressComplete |
uri: Uri |
Success with saved file URI |
SaveProgressError |
exception: FileSaverException |
Error occurred |
SaveProgressCancelled |
- | User cancelled operation |
Exception Reference #
| Exception | Description | Error Code |
|---|---|---|
PermissionDeniedException |
Storage access denied | PERMISSION_DENIED |
FileExistsException |
File exists with fail strategy |
FILE_EXISTS |
StorageFullException |
Insufficient device storage | STORAGE_FULL |
InvalidFileException |
Empty bytes or invalid filename | INVALID_FILE |
FileIOException |
File system error | FILE_IO_ERROR |
UnsupportedFormatException |
Format not supported on platform | UNSUPPORTED_FORMAT |
SourceFileNotFoundException |
Source file not found (saveFile) | FILE_NOT_FOUND |
ICloudDownloadException |
iCloud download failed (iOS) | ICLOUD_DOWNLOAD_FAILED |
NetworkException |
Network error occurred during download. | NETWORK_ERROR |
CancelledException |
Operation cancelled by user | CANCELLED |
PlatformException |
Generic platform error | PLATFORM_ERROR |
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
Future Features #
File Input MethodsSave from Network URL- User-Selected Location Android (SAF), iOS (Document Picker)
- Custom Path Support
Progress TrackingCancellation SupportSave from File Path- MacOS Support
- Windows Support
- Web Support
License #
MIT License - see LICENSE file for details.