file_saver_ffi 0.0.2 copy "file_saver_ffi: ^0.0.2" to clipboard
file_saver_ffi: ^0.0.2 copied to clipboard

A Flutter plugin for saving files to device storage using FFI and JNI

cover

File Saver FFI #

Pub Platform License: MIT

A high-performance Flutter plugin for saving files, images, and videos to device storage using native APIs via FFI (iOS) and JNI (Android).

Features #

  • Native Performance - FFI (iOS) and JNI (Android) for maximum speed
  • 📁 Universal File Saving - Save any file type to device storage with a single method
  • 🖼️ Image-Specific Handling - Format validation and album support
  • 🎥 Video Handling - Native integration with Photos (iOS) and MediaStore (Android)
  • ⚙️ Conflict Resolution - Auto-rename, overwrite, skip, or fail on conflicts
  • 🎯 Album/Subdirectory Support - Organize all file types in albums (iOS) or subdirectories (Android)
  • 💾 Original Quality - Always saves at original quality, no compression
  • 🔒 Type-Safe API - Sealed classes and pattern matching for robust code
  • 📂 Smart Location Routing - Files automatically saved to appropriate directories based on type

If you want to say thank you, star us on GitHub or like us on pub.dev.

Supported Platforms #

Platform Minimum Version Notes
Android API 21+ (Android 5.0+) Scoped storage for Android 10+
iOS 13.0+ Photos framework with album support

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+ (API 29+) uses scoped storage automatically and does not require this permission.

iOS Configuration #

Add to ios/Runner/Info.plist:

For Photos Library Access (Required for 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>

For Files App Visibility (Optional for custom files)

Files are saved to the Application Documents Directory. To make them visible to users in the Files app, add:


<key>UIFileSharingEnabled</key>
<true/>

<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>

Quick Start #

import 'package:file_saver_ffi/file_saver_ffi.dart';

try {
  // Save image bytes
  final uri = await FileSaver.instance.saveBytes(
    bytes: 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}');
}

Resource Management #

FileSaver uses native resources via FFI (iOS) and JNI (Android). The library provides automatic cleanup via NativeFinalizer, but you can also manually release resources if needed.

Manual Disposal #

If you want to release native resources immediately (e.g., to free memory sooner), call dispose():

// Release resources immediately when you're done
FileSaver.instance.dispose();

App Lifecycle Integration (Optional) #

For explicit cleanup when the app terminates, you can use WidgetsBindingObserver:

import 'package:file_saver_ffi/file_saver_ffi.dart';
import 'package:flutter/material.dart';

class AppLifecycleObserver extends WidgetsBindingObserver {
  final VoidCallback? onDetached;

  AppLifecycleObserver({this.onDetached});

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.detached) {
      onDetached?.call();
    }
  }
}

void main() {
  final binding = WidgetsFlutterBinding.ensureInitialized();
  
  binding.addObserver(
    AppLifecycleObserver(
      onDetached: FileSaver.instance.dispose,
    ),
  );
  
  runApp(const MyApp());
}

Note: AppLifecycleState.detached is not guaranteed to be called on all platforms when the app is force-killed. However, the OS will automatically reclaim all memory when the process terminates, so this is primarily for explicit cleanup in normal shutdown scenarios.


## Supported File Types

### Images (12 formats)

`PNG`, `JPG`, `JPEG`, `GIF`, `WebP`, `BMP`, `HEIC`, `HEIF`, `TIFF`, `TIF`, `ICO`, `DNG`

```dart
ImageType.png
ImageType.jpg
ImageType.gif
ImageType.webp
// ... and more

Videos (12 formats) #

MP4, 3GP, WebM, M4V, MKV, MOV, AVI, FLV, WMV, HEVC, VP9, AV1

VideoType.mp4
VideoType.mov
VideoType.mkv
// ... and more

Audio (11 formats) #

MP3, AAC, WAV, AMR, 3GP, M4A, OGG, FLAC, Opus, AIFF, CAF

AudioType.mp3
AudioType.aac
AudioType.wav
// ... and more

Custom File Types #

Support any file format by specifying extension and MIME type:

CustomFileType(
  ext: 'pdf',
  mimeType: 'application/pdf'
)
CustomFileType(
  ext: 'docx', 
  mimeType:'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
)

Conflict Resolution Strategies #

Control what happens when a file with the same name already exists:

Strategy Behavior Use Case
autoRename (default) Appends (1), (2), etc. to filename Safe, prevents data loss
overwrite Replaces existing file Update existing files
fail Returns SaveFailure with "FILE_EXISTS" error Strict validation
skip Returns SaveSuccess with existing file path Idempotent saves

Example #

try {
  final uri = await FileSaver.instance.saveBytes(
    bytes: fileBytes,
    fileName: 'document',
    fileType: CustomFileType(ext: 'pdf', mimeType: 'application/pdf'),
    conflictResolution: ConflictResolution.autoRename,
  );
  
    // If "document.pdf" exists, saves as "document (1).pdf"
    print('Saved to: $uri');
  } on FileSaverException catch (e) {
    print('Error: ${e.message}');
}

Advanced Usage #

Save with Subdirectory/Album #

try {
  final uri = await FileSaver.instance.saveBytes(
    bytes: videoBytes,
    fileName: 'vacation_video',
    fileType: VideoType.mp4,
    subDir: 'My Vacations', // Creates album on iOS, folder on Android
  );
  
    print('Video saved to: $uri');
  } on FileSaverException catch (e) {
    print('Error: ${e.message}');
}

Complete Example with Error Handling #

try {
  final uri = await FileSaver.instance.saveBytes(
    bytes: pdfBytes,
    fileName: 'invoice_${DateTime.now().millisecondsSinceEpoch}',
    fileType: CustomFileType(ext: 'pdf', mimeType: 'application/pdf'),
    subDir: 'Invoices',
    conflictResolution: ConflictResolution.autoRename,
  );
  
    print('✅ Saved successfully!');
    print('URI: $uri');
  
} on PermissionDeniedException catch (e) {
  print('❌ Permission denied: ${e.message}');
  // Request permissions

} on FileExistsException catch (e) {
  print('❌ File already exists: ${e.fileName}');
  // Handle conflict

} on StorageFullException catch (e) {
  print('❌ Storage full: ${e.message}');
  // Show storage full message

} on InvalidFileException catch (e) {
  print('❌ Invalid file: ${e.message}');
  // Validate file data

} on FileSaverException catch (e) {
  print('❌ Save failed: ${e.message}');
  // Generic error handling
}

Platform-Specific Behavior #

File Storage Locations #

Android

Files are saved to MediaStore collections based on type:

File Type Location
Images Pictures/[subDir]/
Videos Movies/[subDir]/
Audio Music/[subDir]/
Custom Files Downloads/[subDir]/

URI Format: content://media/external/...

iOS

Files are saved to platform-appropriate locations:

File Type Location
Images Photos library album [subDir]
Videos Photos library album [subDir]
Audio Photos library (if supported)
Custom Files Documents/[subDir]/ (visible in Files app if configured)

URI Format: ph:// for Photos, file:// for Documents

SubDir Parameter #

  • iOS: Creates an album in the Photos app with the specified name
  • Android: Creates a folder in the appropriate MediaStore collection

Example:

// iOS: Creates "My App" album in Photos
// Android: Creates Pictures/My App/ folder
subDir: 'My App'

Error Handling #

The library provides specific exception types for different failure scenarios:

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
UnsupportedFormatException Format not supported on platform UNSUPPORTED_FORMAT
PlatformException Generic platform-specific error PLATFORM_ERROR

Handling Errors #

try {
  final uri = await FileSaver.instance.saveBytes(...);
  print('Saved to: $uri');

} on PermissionDeniedException catch (e) {
    // Request permissions

} on StorageFullException catch (e) {
  // Show storage full message

} on FileExistsException catch (e) {
  // File already exists: ${e.fileName}

} on FileSaverException catch (e) {
  // Generic error handling
}

API Reference #

FileSaver #

Singleton API class for saving files.

Future<Uri> saveBytes({
  required Uint8List bytes,
  required String fileName,
  required FileType fileType,
  String? subDir,
  ConflictResolution conflictResolution = ConflictResolution.autoRename,
})

Throws FileSaverException or subtypes on failure.

ConflictResolution #

Enum for conflict resolution strategies:

enum ConflictResolution {
  autoRename, // Append (1), (2), etc.
  overwrite, // Replace existing file
  fail, // Return error
  skip, // Return existing file path
}

Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

Future Features #

  • File Input Methods
  • Save from Network URL
  • User-Selected Location Android (SAF), iOS (Document Picker)
  • Custom Path Support
  • Progress Tracking
  • MacOS Support
  • Windows Support
  • Web Support
1
likes
160
points
87
downloads
screenshot

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for saving files to device storage using FFI and JNI

Repository (GitHub)
View/report issues

Topics

#file-saver #file-saver-ffi #file-saver-native #ffi #jni

Documentation

API reference

License

MIT (license)

Dependencies

ffi, flutter, jni

More

Packages that depend on file_saver_ffi

Packages that implement file_saver_ffi