file_saver_ffi 0.4.0
file_saver_ffi: ^0.4.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.
0.4.0 #
Added #
- macOS Support: Save files to macOS directories using FFI with shared Darwin source code
- Android: Automatic storage permission handling for Android 9 and below
Fixed #
- Network save: file deleted immediately after successful download
invalidateAndCancel()triggereddidCompleteWithErrorwith cancellation error, which deleted the saved file- Replaced with
finishTasksAndInvalidate()to allow the task to complete normally - Affected all platforms using native network save (
saveNetwork/saveNetworkAs)
- iOS: Network save to Photos requested permission after download
- Permission and conflict resolution are now checked before starting the download
- Avoids wasting bandwidth if permission is denied or file already exists (skip/fail)
0.3.1 #
Fixed #
- iOS: Photos Library crash on iOS 14+ with add-only permission (
PHPhotosErrorDomain error 3311)- Previously requested
.addOnlypermission but incorrectly treated it as having read access - This caused crashes when attempting album creation or conflict resolution without read permission
- Now requests the appropriate permission level based on usage:
- With
subDir(album name): requests.readWritefor album & conflict resolution support - Without
subDir: requests.addOnlyfor basic save
- With
- Previously requested
0.3.0 #
Breaking Changes #
- Renamed
InvalidFileExceptiontoInvalidInputException- Error code changed from
INVALID_FILEtoINVALID_INPUT - This aligns error codes between iOS and Android platforms
- Migration: Replace
InvalidFileExceptionwithInvalidInputExceptionin your catch blocks
- Error code changed from
Added #
- User-Selected Directory Support:
pickDirectory(): Show system directory picker (Android SAF / iOS Document Picker)saveAs(): Stream-based API to save to user-selected directory usingSaveInputsaveAsync(): Async wrapper for [saveAs] with optional progress callback.
- Save Locations Update: Add UserSelectedLocation for User-selected directory location
0.2.0 #
Added #
-
Network Save Support:
saveNetwork(): Stream-based API to download and save files directly from URLssaveNetworkAsync(): Future-based API for network downloads with optional progress callback- Native optimization: Downloads directly to storage to avoid double storage (memory + disk)
- Supports custom HTTP headers and timeouts
-
Unified API Entrypoints:
save(): Single stream-based entrypoint usingSaveInputsealed class (SaveBytesInput,SaveFileInput,SaveNetworkInput)saveAsync(): Single future-based entrypoint usingSaveInput- Allows polymorphic usage:
FileSaver.instance.save(input: SaveNetworkInput(...))
Refactored #
- Source code refactoring
0.1.1 #
Documentation #
- Save Locations Update: Split table into separate
AndroidSaveLocationandIosSaveLocationsections to clarify distinct enums
0.1.0 #
Breaking Changes #
saveBytes()now returnsStream<SaveProgress>instead ofFuture<Uri>- Renamed
saveBytesparameter frombytestofileBytes. - Renamed
saveBytesAsyncparameter frombytestofileBytes.
- Renamed
Added #
-
saveFile()method: Save files from source path instead of bytes in memory- Stream-based API returning
Stream<SaveProgress>for real-time progress tracking - Handles large files efficiently without loading entire file into RAM
- Supports
file://paths and picker results
- Stream-based API returning
-
saveFileAsync()method: Convenience API returningFuture<Uri>with optionalonProgresscallbackfinal uri = await FileSaver.instance.saveFileAsync( filePath: '/path/to/source/video.mp4', fileName: 'my_video', fileType: VideoType.mp4, onProgress: (progress) => print('${(progress * 100).toInt()}%'), ); -
True Cancellation Support: Cancel save operations mid-stream with proper cleanup
- Call
subscription.cancel()or break fromawait forloop to cancel - Native code stops I/O operations between chunks (1MB)
- Partial files are automatically deleted on cancellation
SaveProgressCancelledevent emitted on successful cancellation- Works for both
saveBytes()andsaveFile()streams
// Example: Cancel with subscription final subscription = FileSaver.instance.saveBytes(...).listen((event) { if (event is SaveProgressCancelled) { print('Cancelled and cleaned up!'); } }); // Cancel when needed await subscription.cancel(); // Example: Cancel with await for await for (final event in FileSaver.instance.saveBytes(...)) { if (shouldCancel) break; // Triggers native cancellation // handle event... } - Call
-
iOS iCloud download progress: When saving files from iCloud Drive, progress is split into 2 phases:
- Phase 1 (0% → 50%): iCloud download progress
- Phase 2 (50% → 100%): Copy to destination progress
-
New exceptions:
FileNotFoundException: Source file not foundICloudDownloadException: iCloud download failed or timed out (iOS only)
Changed #
- Simplified FormatValidator: Removed codec/encoder validation on both platforms
- iOS: Removed AVAssetWriter and ImageIO validation
- Android: Removed MediaCodecList encoder checks
- Now only validates MIME type category (image/video/audio)
- Rationale: This library is a file saver, not a media player. Files are written as raw bytes without encoding/decoding. The developer is responsible for choosing appropriate formats.
Fixed #
- iOS video filename: Videos saved to Photos Library now preserve the specified filename instead of using UUID
- iOS audio format crash: Fixed crash when saving MP3 and other audio formats that AVAssetWriter doesn't support
- Android audio format rejection: Fixed rejection of audio formats not in MediaCodecList (AMR, OPUS, etc.)
0.0.5 #
Breaking Changes #
saveBytes()now returnsStream<SaveProgress>instead ofFuture<Uri>- Enables real-time progress tracking during save operations
- Use
saveBytesAsync()for the previousFuture<Uri>behavior
Added #
SaveProgresssealed class for streaming progress events:SaveProgressStarted- Operation startedSaveProgressUpdate(double progress)- Progress 0.0 to 1.0SaveProgressComplete(Uri uri)- Success with file URISaveProgressError(FileSaverException)- Error occurredSaveProgressCancelled- User cancelled
saveBytesAsync()method - Convenience API returningFuture<Uri>with optionalonProgresscallback- Real progress reporting for iOS - Chunked file writes with progress callbacks (1MB chunks)
Migration Guide #
// Before (0.0.4)
final uri = await
FileSaver.instance.saveBytes
(...);
// After (0.0.5) - Option 1: Use saveBytesAsync (minimal change)
final uri = await FileSaver.instance.saveBytesAsync(...);
// After (0.0.5) - Option 2: Use saveBytesAsync with progress
final uri = await FileSaver.instance.saveBytesAsync(
...,
onProgress: (progress) => print('${(progress * 100).toInt()}%'),
);
// After (0.0.5) - Option 3: Use saveBytes stream for full control
await for (final event in FileSaver.instance.saveBytes(...)) {
switch (event) {
case SaveProgressStarted(): showLoading();
case SaveProgressUpdate(:final progress): updateUI(progress);
case SaveProgressComplete(:final uri): handleSuccess(uri);
case SaveProgressError(:final exception): handleError(exception);
case SaveProgressCancelled(): handleCancel();
}
}
0.0.4 #
Added #
- SaveLocation Feature: Explicit control over save locations with platform-specific enums
- Android:
pictures,movies,music,downloads(default),dcim - iOS:
photos(Photos Library),documents(default, no permission) - Type-safe sealed class design with platform defaults
- Android:
Changed #
- Added optional
saveLocationparameter tosaveBytes() - Standardized parameter order:
saveLocationnow beforesubDir
Breaking Changes #
- Default locations changed for better UX:
- Android: All files → Downloads (was type-based: Images→Pictures, Videos→Movies, etc.)
- iOS: All files → Documents (was Images/Videos→Photos Library)
- Migration: Explicitly set
saveLocationto maintain old behavior:saveLocation: Platform.isAndroid ? AndroidSaveLocation.pictures : [PlatformX]SaveLocation.photos
0.0.3 #
Added #
- OVERWRITE Functionality: Fully implemented overwrite conflict resolution
- Android (Legacy): Delete existing file and save new one
- Android 10+: Delete existing file via ContentResolver
- iOS: Optimized with early return check
- Platform Behavior Documentation: Comprehensive guide for overwrite behavior
- iOS Photos: Own files overwritten; other apps' files create duplicates
- iOS Documents: Full overwrite capability (sandboxed per app)
- Android 10+: Only detects/overwrites own files; other apps' files auto-renamed
- Platform comparison table in README
- iOS 14+ Dialog Prevention: Added
PHPhotoLibraryPreventAutomaticLimitedAccessAlertkey- Prevents automatic "Select More Photos" prompt on iOS 14+
- Provides better user experience with limited photos access
- Documented in README with setup instructions
Refactored #
- iOS Code Quality: Extracted common logic from ImageSaver and VideoSaver
- Moved
findOrCreateAlbum()to BaseFileSaver extension - Moved
handlePhotosConflictResolution()to BaseFileSaver extension - Removed 38 lines of duplicated code for better maintainability
- Moved
0.0.2 #
- Refactor
FileSaverIosto use NativeFinalizer + Arena for safer native resource management, more robust, and less prone to native memory leaks while maintaining performance. - Make
FileSaverPlatform.instancea true singleton - Update document and README.md