fast_file_picker
is a fast file picker for Flutter. To achieve the best performance, it returns OS file info directly and never performs any file copying or conversion. It supports picking files, folders, and save paths on all platforms. Partly based on file_selector.
iOS | Android | macOS | Windows / Linux | |
---|---|---|---|---|
Pick files | ✅ (Name/Path/URL) | ✅ (Name/Uri) | ✅ (Name/Path/URL) | ✅ (Name/Path) |
Pick a folder | ✅ (Name/Path/URL) | ✅ (Name/Uri) | ✅ (Name/Path/URL) | ✅ (Name/Path) |
Pick a save path | ✅ (Name/Path/URL) | ✅ (Name/Path) |
- For each operation, please follow the platform-specific notes below.
⚠️ : It's recommended to use mobile share menu on iOS and Android to save files.
On macOS, you need to add the following key to entitlements in order for macOS app to be able to access file system:
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
Since fast_file_picker
can return path or URI or both, the result is wrapped in FastFilePickerPath
:
class FastFilePickerPath {
final String name;
final String? path;
final String? uri;
}
Platform notes:
- Windows / macOS / Linux: Use Dart IO to access the file.
- iOS: call
useAppleScopedResource
oraccessAppleScopedResource
to gain access first, then use Dart IO to access the file. See 'Apple scoped resource' section below for details. - Android: use saf_stream for file reading or saf_util for file info.
class FastFilePicker {
/// Picks a file and return a [FastFilePickerPath].
/// If the user cancels the picker, it returns `null`.
static Future<FastFilePickerPath?> pickFile();
/// Picks multiple files and return a list of [FastFilePickerPath].
/// If the user cancels the picker, it returns `null`.
static Future<List<FastFilePickerPath>?> pickMultipleFiles();
}
Example:
final files = await FastFilePicker.pickMultipleFiles();
if (files == null) {
setState(() {
_output = 'User canceled the picker';
});
return;
}
// Handle selected files.
for (final file in files) {
if (Platform.isIOS) {
// Handle iOS file.
// Use [useAppleScopedResource] to request access to the file.
final hasAccess = await file.useAppleScopedResource((file) async {
// Callback gets called only if access is granted.
// Now you can read the file with Dart's IO.
final bytes = await File(file.path!).readAsBytes();
});
if (hasAccess != true) {
debugPrint('No access to file');
}
} else if (file.uri != null && Platform.isAndroid) {
// Handle Android file.
// For example, use [saf_stream] package to read the file.
final bytes = await _safStream.readFileBytes(file.uri!);
} else if (file.path != null) {
// Handle Windows / macOS / Linux file.
final bytes = await File(file.path!).readAsBytes();
}
}
Platform notes:
- Windows / macOS / Linux: Use Dart IO to access the folder.
- iOS: call
useAppleScopedResource
oraccessAppleScopedResource
to gain access first, then use Dart IO to access the folder. See 'Apple scoped resource' section below for details. - Android: use saf_stream for file IO inside the folder or saf_util for folder operations.
class FastFilePicker {
/// Picks a folder and return a [FastFilePickerPath].
/// If the user cancels the picker, it returns `null`.
///
/// [writePermission] is only applicable on Android.
static Future<FastFilePickerPath?> pickFolder(
{required bool writePermission});
}
Example:
// Handle selected folder.
final folder = await FastFilePicker.pickFolder(
writePermission: false);
if (folder == null) {
setState(() {
_output = 'Cancelled';
});
return;
}
if (Platform.isIOS) {
// Handle iOS folder.
// Use [useAppleScopedResource] to request access to the folder.
final hasAccess = await folder
.useAppleScopedResource((folder) async {
// Callback gets called only if access is granted.
// You can access the folder only if [hasAccess] is true.
final subFileNames =
(await Directory(folder.path!).list().toList())
.map((e) => e.path);
setState(() {
_output =
'Folder: $folder\n\nSubfiles: $subFileNames';
});
});
if (hasAccess != true) {
setState(() {
_output = 'No access to folder';
});
}
} else if (Platform.isAndroid && folder.uri != null) {
// Handle Android folder.
// Use [saf_util] package to list files in the folder.
// Or use [saf_stream] package to read the files.
final subFileNames = (await _safUtil.list(folder.uri!))
.map((e) => e.name);
setState(() {
_output =
'Folder: $folder\n\nSubfiles: $subFileNames';
});
} else if (folder.path != null) {
// Handle Windows / macOS / Linux folder.
final subFileNames =
(await Directory(folder.path!).list().toList())
.map((e) => e.path);
setState(() {
_output =
'Folder: $folder\n\nSubfiles: $subFileNames';
});
}
Platform notes:
- iOS / Android: Not supported. It's recommended to use mobile share menu to save files.
- Windows / macOS / Linux: Use Dart IO to handle the save path.
class FastFilePicker {
/// Picks a save file location and return a [String] path.
/// You can optionally specify a default file name via [defaultName].
/// If the user cancels the picker, it returns `null`.
static Future<String?> pickSaveFile({String? defaultName});
}
Example:
final savePath = await FastFilePicker.pickSaveFile();
if (savePath == null) {
setState(() {
_output = 'User canceled the picker';
});
return;
}
// Handle save path using Dart IO.
On iOS and macOS (if you are dealing with iCloud files), you need to request access to the file or folder before accessing it. fast_file_picker
has extension methods on FastFilePickerPath
to help you with this. Namely accessAppleScopedResource
and releaseAppleScopedResource
.
final pickerResult = await FastFilePickerUtil.pickFile(); // or pickFolder().
if (pickerResult == null) {
// User canceled the picker.
return;
}
if (Platform.isIOS) {
final hasAccess = await pickerResult.accessAppleScopedResource();
try {
if (hasAccess != true) {
// Denied access or not supported.
return;
}
/** Access the file or folder */
} finally {
// Always release the access when done.
await pickerResult.releaseAppleScopedResource(hasAccess);
}
}
There is also a shorthand method useAppleScopedResource
that combines the above two methods:
final pickerResult = await FastFilePickerUtil.pickFile(); // or pickFolder().
if (pickerResult == null) {
// User canceled the picker.
return;
}
if (Platform.isIOS) {
// This will automatically release the access when done.
// If platform is not supported or access is denied, callback will not be called.
final hasAccess = await pickerResult.useAppleScopedResource((file) async {
/** Access granted */
/** Process the resource */
});
if (hasAccess != true) {
// Denied access or not supported.
return;
}
}