Allow picking directory path

Adds support for picking directory paths on both iOS & Android through getDirectoryPath() method.
This commit is contained in:
Miguel Ruivo 2020-06-04 22:49:39 +01:00
parent 2214e87c4a
commit b1f8cd6064
12 changed files with 190 additions and 97 deletions

View File

@ -1,3 +1,6 @@
## 1.10.0
**Android & iOS:** Adds `getDirectoryPath()` method that allows you to select and pick directory paths. Android, requires SDK 21 or above for this to work, and iOS requires iOS 11 or above.
## 1.9.0+1
Adds a temporary workaround on Android where it can trigger `onRequestPermissionsResult` twice, related to Flutter issue [49365](https://github.com/flutter/flutter/issues/49365) for anyone affected in Flutter versions below 1.14.6.

View File

@ -25,6 +25,7 @@ A package that allows you to use a native file explorer to pick single or multip
* Load path from **audio** only
* Load path from **image** only
* Load path from **video** only
* Load path from **directory**
* Load path from **any**
* Create a `File` or `List<File>` objects from **any** selected file(s)
* Supports desktop through **go-flutter** (MacOS, Windows, Linux)

View File

@ -5,7 +5,9 @@ import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@ -85,12 +87,17 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
finishWithSuccess(paths.get(0));
}
} else if (data.getData() != null) {
final Uri uri = data.getData();
Uri uri = data.getData();
String fullPath;
if (type.equals("dir") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
uri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
}
Log.i(FilePickerDelegate.TAG, "[SingleFilePick] File URI:" + uri.toString());
String fullPath = FileUtils.getPath(uri, FilePickerDelegate.this.activity);
fullPath = FileUtils.getPath(uri, FilePickerDelegate.this.activity);
if (fullPath == null) {
fullPath = FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, uri);
fullPath = type.equals("dir") ? FileUtils.getFullPathFromTreeUri(uri, activity) : FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, uri);
}
if (fullPath != null) {
@ -99,6 +106,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
} else {
finishWithError("unknown_path", "Failed to retrieve path.");
}
} else {
finishWithError("unknown_activity", "Unknown activity error, please fill an issue.");
}
@ -160,20 +168,24 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
return;
}
intent = new Intent(Intent.ACTION_GET_CONTENT);
final Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + File.separator);
Log.d(TAG, "Selected type " + type);
intent.setDataAndType(uri, this.type);
intent.setType(this.type);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, this.isMultipleSelection);
intent.addCategory(Intent.CATEGORY_OPENABLE);
if (type.equals("dir")) {
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
} else {
intent = new Intent(Intent.ACTION_GET_CONTENT);
final Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + File.separator);
Log.d(TAG, "Selected type " + type);
intent.setDataAndType(uri, this.type);
intent.setType(this.type);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, this.isMultipleSelection);
intent.addCategory(Intent.CATEGORY_OPENABLE);
if (type.contains(",")) {
allowedExtensions = type.split(",");
}
if (type.contains(",")) {
allowedExtensions = type.split(",");
}
if (allowedExtensions != null) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, allowedExtensions);
if (allowedExtensions != null) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, allowedExtensions);
}
}
if (intent.resolveActivity(this.activity.getPackageManager()) != null) {

View File

@ -152,13 +152,16 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
}
fileType = FilePickerPlugin.resolveType(call.method);
isMultipleSelection = (boolean) arguments.get("allowMultipleSelection");
final String[] allowedExtensions = FileUtils.getMimeTypes((ArrayList<String>) arguments.get("allowedExtensions"));
String[] allowedExtensions = null;
if (fileType == null) {
result.notImplemented();
} else if (fileType == "custom" && (allowedExtensions == null || allowedExtensions.length == 0)) {
} else if (fileType != "dir") {
isMultipleSelection = (boolean) arguments.get("allowMultipleSelection");
allowedExtensions = FileUtils.getMimeTypes((ArrayList<String>) arguments.get("allowedExtensions"));
}
if (fileType == "custom" && (allowedExtensions == null || allowedExtensions.length == 0)) {
result.error(TAG, "Unsupported filter. Make sure that you are only using the extension without the dot, (ie., jpg instead of .jpg). This could also have happened because you are using an unsupported file extension. If the problem persists, you may want to consider using FileType.all instead.", null);
} else {
this.delegate.startFileExplorer(fileType, isMultipleSelection, allowedExtensions, result);
@ -168,7 +171,6 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
private static String resolveType(final String type) {
switch (type) {
case "audio":
return "audio/*";
@ -181,6 +183,8 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
case "any":
case "custom":
return "*/*";
case "dir":
return "dir";
default:
return null;
}

View File

@ -1,5 +1,6 @@
package com.mr.flutter.plugin.filepicker;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
@ -7,23 +8,29 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Random;
public class FileUtils {
private static final String TAG = "FilePickerUtils";
private static final String PRIMARY_VOLUME_NAME = "primary";
public static String getPath(final Uri uri, final Context context) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
@ -53,7 +60,7 @@ public class FileUtils {
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
Log.e(TAG, "Primary External Document URI");
return Environment.getExternalStorageDirectory() + "/" + split[1];
return Environment.getExternalStorageDirectory() + (split.length > 1 ? ("/" + split[1]) : "");
}
} else if (isDownloadsDocument(uri)) {
Log.e(TAG, "Downloads External Document URI");
@ -270,6 +277,78 @@ public class FileUtils {
return externalFile;
}
@Nullable
public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
if (treeUri == null) return null;
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), con);
if (volumePath == null) return File.separator;
if (volumePath.endsWith(File.separator))
volumePath = volumePath.substring(0, volumePath.length() - 1);
String documentPath = getDocumentPathFromTreeUri(treeUri);
if (documentPath.endsWith(File.separator))
documentPath = documentPath.substring(0, documentPath.length() - 1);
if (documentPath.length() > 0) {
if (documentPath.startsWith(File.separator))
return volumePath + documentPath;
else
return volumePath + File.separator + documentPath;
} else return volumePath;
}
@SuppressLint("ObsoleteSdkInt")
private static String getVolumePath(final String volumeId, Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
try {
StorageManager mStorageManager =
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getUuid = storageVolumeClazz.getMethod("getUuid");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String uuid = (String) getUuid.invoke(storageVolumeElement);
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
// primary volume?
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
// other volumes?
if (uuid != null && uuid.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
}
// not found.
return null;
} catch (Exception ex) {
return null;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if (split.length > 0) return split[0];
else return null;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if ((split.length >= 2) && (split[1] != null)) return split[1];
else return File.separator;
}
private static boolean isDropBoxUri(final Uri uri) {
return "com.dropbox.android.FileCache".equals(uri.getAuthority());
}

View File

@ -71,7 +71,7 @@ SPEC CHECKSUMS:
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b
flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35
SDWebImage: 97351f6582ceca541ea294ba66a1fcb342a331c2
SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8

View File

@ -35,7 +35,6 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
4FE42FA345519DC2CF8F7CAB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
620D3249E16C66CF31F39A1D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@ -43,7 +42,6 @@
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
@ -67,9 +65,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,

View File

@ -31,17 +31,11 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
if (_multiPick) {
_path = null;
_paths = await FilePicker.getMultiFilePath(
type: _pickingType,
allowedExtensions: (_extension?.isNotEmpty ?? false)
? _extension?.replaceAll(' ', '')?.split(',')
: null);
type: _pickingType, allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '')?.split(',') : null);
} else {
_paths = null;
_path = await FilePicker.getFilePath(
type: _pickingType,
allowedExtensions: (_extension?.isNotEmpty ?? false)
? _extension?.replaceAll(' ', '')?.split(',')
: null);
type: _pickingType, allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '')?.split(',') : null);
}
} on PlatformException catch (e) {
print("Unsupported operation" + e.toString());
@ -49,9 +43,7 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
if (!mounted) return;
setState(() {
_loadingPath = false;
_fileName = _path != null
? _path.split('/').last
: _paths != null ? _paths.keys.toString() : '...';
_fileName = _path != null ? _path.split('/').last : _paths != null ? _paths.keys.toString() : '...';
});
}
@ -60,14 +52,18 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
_scaffoldKey.currentState.showSnackBar(
SnackBar(
backgroundColor: result ? Colors.green : Colors.red,
content: Text((result
? 'Temporary files removed with success.'
: 'Failed to clean temporary files')),
content: Text((result ? 'Temporary files removed with success.' : 'Failed to clean temporary files')),
),
);
});
}
void _selectFolder() {
FilePicker.getDirectoryPath().then((value) {
setState(() => _path = value);
});
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
@ -128,8 +124,7 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
maxLength: 15,
autovalidate: true,
controller: _controller,
decoration:
InputDecoration(labelText: 'File extension'),
decoration: InputDecoration(labelText: 'File extension'),
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.none,
)
@ -138,10 +133,8 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
new ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 200.0),
child: new SwitchListTile.adaptive(
title: new Text('Pick multiple files',
textAlign: TextAlign.right),
onChanged: (bool value) =>
setState(() => _multiPick = value),
title: new Text('Pick multiple files', textAlign: TextAlign.right),
onChanged: (bool value) => setState(() => _multiPick = value),
value: _multiPick,
),
),
@ -153,6 +146,10 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
onPressed: () => _openFileExplorer(),
child: new Text("Open file picker"),
),
new RaisedButton(
onPressed: () => _selectFolder(),
child: new Text("Pick folder"),
),
new RaisedButton(
onPressed: () => _clearCachedFiles(),
child: new Text("Clear temporary files"),
@ -162,28 +159,18 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
),
new Builder(
builder: (BuildContext context) => _loadingPath
? Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: const CircularProgressIndicator())
? Padding(padding: const EdgeInsets.only(bottom: 10.0), child: const CircularProgressIndicator())
: _path != null || _paths != null
? new Container(
padding: const EdgeInsets.only(bottom: 30.0),
height: MediaQuery.of(context).size.height * 0.50,
child: new Scrollbar(
child: new ListView.separated(
itemCount: _paths != null && _paths.isNotEmpty
? _paths.length
: 1,
itemCount: _paths != null && _paths.isNotEmpty ? _paths.length : 1,
itemBuilder: (BuildContext context, int index) {
final bool isMultiPath =
_paths != null && _paths.isNotEmpty;
final String name = 'File $index: ' +
(isMultiPath
? _paths.keys.toList()[index]
: _fileName ?? '...');
final path = isMultiPath
? _paths.values.toList()[index].toString()
: _path;
final bool isMultiPath = _paths != null && _paths.isNotEmpty;
final String name = 'File $index: ' + (isMultiPath ? _paths.keys.toList()[index] : _fileName ?? '...');
final path = isMultiPath ? _paths.values.toList()[index].toString() : _path;
return new ListTile(
title: new Text(
@ -192,9 +179,7 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
subtitle: new Text(path),
);
},
separatorBuilder:
(BuildContext context, int index) =>
new Divider(),
separatorBuilder: (BuildContext context, int index) => new Divider(),
)),
)
: new Container(),

View File

@ -0,0 +1,8 @@
<?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>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -54,6 +54,11 @@
return;
}
if([call.method isEqualToString:@"dir"]) {
[self resolvePickDocumentWithMultiPick:NO pickDirectory:YES];
return;
}
NSDictionary * arguments = call.arguments;
BOOL isMultiplePick = ((NSNumber*)[arguments valueForKey:@"allowMultipleSelection"]).boolValue;
if([call.method isEqualToString:@"any"] || [call.method containsString:@"custom"]) {
@ -64,7 +69,7 @@
details:nil]);
_result = nil;
} else if(self.allowedExtensions != nil) {
[self resolvePickDocumentWithMultipleSelection:isMultiplePick];
[self resolvePickDocumentWithMultiPick:isMultiplePick pickDirectory:NO];
}
} else if([call.method isEqualToString:@"video"] || [call.method isEqualToString:@"image"] || [call.method isEqualToString:@"media"]) {
[self resolvePickMedia:[FileUtils resolveMediaType:call.method] withMultiPick:isMultiplePick];
@ -78,13 +83,12 @@
}
#pragma mark - Resolvers
- (void)resolvePickDocumentWithMultipleSelection:(BOOL)allowsMultipleSelection {
- (void)resolvePickDocumentWithMultiPick:(BOOL)allowsMultipleSelection pickDirectory:(BOOL)isDirectory {
@try{
self.documentPickerController = [[UIDocumentPickerViewController alloc]
initWithDocumentTypes: self.allowedExtensions
inMode:UIDocumentPickerModeImport];
initWithDocumentTypes: isDirectory ? @[@"public.folder"] : self.allowedExtensions
inMode: isDirectory ? UIDocumentPickerModeOpen : UIDocumentPickerModeImport];
} @catch (NSException * e) {
Log(@"Couldn't launch documents file picker. Probably due to iOS version being below 11.0 and not having the iCloud entitlement. If so, just make sure to enable it for your app in Xcode. Exception was: %@", e);
_result = nil;

View File

@ -15,8 +15,7 @@ enum FileType {
class FilePicker {
FilePicker._();
static const MethodChannel _channel =
const MethodChannel('miguelruivo.flutter.plugins.filepicker');
static const MethodChannel _channel = const MethodChannel('miguelruivo.flutter.plugins.filepicker');
static const String _tag = 'FilePicker';
/// Returns an iterable `Map<String,String>` where the `key` is the name of the file
@ -25,9 +24,7 @@ class FilePicker {
/// A `List` with [allowedExtensions] can be provided to filter the allowed files to picked.
/// If provided, make sure you select `FileType.custom` as type.
/// Defaults to `FileType.any`, which allows any combination of files to be multi selected at once.
static Future<Map<String, String>> getMultiFilePath(
{FileType type = FileType.any,
List<String> allowedExtensions}) async =>
static Future<Map<String, String>> getMultiFilePath({FileType type = FileType.any, List<String> allowedExtensions}) async =>
await _getPath(describeEnum(type), true, allowedExtensions);
/// Returns an absolute file path from the calling platform.
@ -35,19 +32,15 @@ class FilePicker {
/// Extension filters are allowed with `FileType.custom`, when used, make sure to provide a `List`
/// of [allowedExtensions] (e.g. [`pdf`, `svg`, `jpg`].).
/// Defaults to `FileType.any` which will display all file types.
static Future<String> getFilePath(
{FileType type = FileType.any,
List<String> allowedExtensions}) async =>
static Future<String> getFilePath({FileType type = FileType.any, List<String> allowedExtensions}) async =>
await _getPath(describeEnum(type), false, allowedExtensions);
/// Returns a `File` object from the selected file path.
///
/// This is an utility method that does the same of `getFilePath()` but saving some boilerplate if
/// you are planing to create a `File` for the returned path.
static Future<File> getFile(
{FileType type = FileType.any, List<String> allowedExtensions}) async {
final String filePath =
await _getPath(describeEnum(type), false, allowedExtensions);
static Future<File> getFile({FileType type = FileType.any, List<String> allowedExtensions}) async {
final String filePath = await _getPath(describeEnum(type), false, allowedExtensions);
return filePath != null ? File(filePath) : null;
}
@ -66,20 +59,30 @@ class FilePicker {
///
/// This is an utility method that does the same of `getMultiFilePath()` but saving some boilerplate if
/// you are planing to create a list of `File`s for the returned paths.
static Future<List<File>> getMultiFile(
{FileType type = FileType.any, List<String> allowedExtensions}) async {
final Map<String, String> paths =
await _getPath(describeEnum(type), true, allowedExtensions);
return paths != null && paths.isNotEmpty
? paths.values.map((path) => File(path)).toList()
: null;
static Future<List<File>> getMultiFile({FileType type = FileType.any, List<String> allowedExtensions}) async {
final Map<String, String> paths = await _getPath(describeEnum(type), true, allowedExtensions);
return paths != null && paths.isNotEmpty ? paths.values.map((path) => File(path)).toList() : null;
}
static Future<dynamic> _getPath(String type, bool allowMultipleSelection,
List<String> allowedExtensions) async {
/// Selects a directory and returns its absolute path.
///
/// On Android, this requires to be running on SDK 21 or above, else won't work.
/// Returns `null` if folder path couldn't be resolved.
static Future<String> getDirectoryPath() async {
try {
return await _channel.invokeMethod('dir');
} on PlatformException catch (ex) {
if (ex.code == "unknown_path") {
print(
'[$_tag] Could not resolve directory path. Maybe it\'s a protected one or unsupported (such as Downloads folder). If you are on Android, make sure that you are on SDK 21 or above.');
}
return null;
}
}
static Future<dynamic> _getPath(String type, bool allowMultipleSelection, List<String> allowedExtensions) async {
if (type != 'custom' && (allowedExtensions?.isNotEmpty ?? false)) {
throw Exception(
'If you are using a custom extension filter, please use the FileType.custom instead.');
throw Exception('If you are using a custom extension filter, please use the FileType.custom instead.');
}
try {
dynamic result = await _channel.invokeMethod(type, {
@ -90,16 +93,14 @@ class FilePicker {
if (result is String) {
result = [result];
}
return Map<String, String>.fromIterable(result,
key: (path) => path.split('/').last, value: (path) => path);
return Map<String, String>.fromIterable(result, key: (path) => path.split('/').last, value: (path) => path);
}
return result;
} on PlatformException catch (e) {
print('[$_tag] Platform exception: $e');
rethrow;
} catch (e) {
print(
'[$_tag] Unsupported operation. Method not found. The exception thrown was: $e');
print('[$_tag] Unsupported operation. Method not found. The exception thrown was: $e');
rethrow;
}
}

View File

@ -1,7 +1,7 @@
name: file_picker
description: A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.
homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker
version: 1.9.0+1
version: 1.10.0
dependencies:
flutter: