Updates docs

This commit is contained in:
Miguel Ruivo 2020-09-11 18:37:45 +01:00
parent 1239c39113
commit 4da8c0f180
8 changed files with 131 additions and 49 deletions

View File

@ -1,3 +1,20 @@
## 2.0.0
**Breaking Changes**
- Unifies all platforms (IO, Web and Desktop) in a single plugin (file_picker) that can be used seamlessly across all. Both [file_picker_interface](https://pub.dev/packages/file_picker_platform_interface) and [file_picker_web](https://pub.dev/packages/file_picker_web) are no longer mantained from now on.
- You'll now have to use `FilePicker.platform.pickFiles()` and extract the files from `FilePickerResult`;
- Because platforms are unified and mixing `File` instances from dart:io and dart:html required stubbing and bloated code, it's no longer available as helper method so you'll have to instanciate a `File` with the picked paths;
**New features**
- Simplified usage, now you only need to use `FilePicker.platform.pickFiles()` with desired parameters which will return a `FilePickerResult` with a `List<PlatformFile>` containing the picked data;
- Added classes `FilePickerResult` and `PlatformFile` classes with helper getters;
- On Android all picked files are scoped cached which should result in most of files being available. Caching process is only made once, so once done, the picked instance should be the same;
- On iOS picking audio now supports multiple and cloud picks;
- Added parameter `withData` that allows file data to be immediately available on memory as `Uint8List` (part of `PlatformFile` instance). This is particularly helpful on web or if you are going to upload to somehwere else;
- Major refactor with some clean-up and improvements;
**Removed**
- Single methods such as `getFilePath()`, `getMultiFilePath()`, `getFile()` and `getMultiFile()` are no longer availble in favor o `pickFiles()`;
## 1.13.3 ## 1.13.3
Go: Updates MacOS directory picker to applescript (thank you @trister1997). Go: Updates MacOS directory picker to applescript (thank you @trister1997).

View File

@ -18,16 +18,17 @@
A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extensions filtering support. A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extensions filtering support.
## Currently supported features ## Currently supported features
* Load paths from **cloud files** (GDrive, Dropbox, iCloud) * Load files from **cloud files** (GDrive, Dropbox, iCloud)
* Load path from a **custom format** by providing a list of file extensions (pdf, svg, zip, etc.) * Load files from a **custom format** by providing a list of file extensions (pdf, svg, zip, etc.)
* Load path from **multiple files** optionally, supplying file extensions * Load files from **multiple files** optionally, supplying file extensions
* Load path from **media** (video & image only) * Load files from **media** (video & image only)
* Load path from **audio** only * Load files from **audio** only
* Load path from **image** only * Load files from **image** only
* Load path from **video** only * Load files from **video** only
* Load path from **directory** * Load files from **directory**
* Load path from **any** * Load files from **any**
* Create a `File` or `List<File>` objects from **any** selected file(s) * Load files data immediately to memory (`Uint8List`);
* Supports web;
* Supports desktop through **go-flutter** (MacOS, Windows, Linux) * Supports desktop through **go-flutter** (MacOS, Windows, Linux)
If you have any feature that you want to see in this package, please feel free to issue a suggestion. 🎉 If you have any feature that you want to see in this package, please feel free to issue a suggestion. 🎉
@ -54,19 +55,42 @@ Quick simple usage example:
#### Single file #### Single file
``` ```
File file = await FilePicker.getFile(); FilePickerResult result = await FilePicker.platform.pickFiles();
if(result != null) {
File file = File(result.files.single.path);
}
``` ```
#### Multiple files #### Multiple files
``` ```
List<File> files = await FilePicker.getMultiFile(); FilePickerResult result = await FilePicker.platform.pickFiles(allowMultiple: true);
if(result != null) {
List<File> files = result.paths.map((path) => File(path));
}
``` ```
#### Multiple files with extension filter #### Multiple files with extension filter
``` ```
List<File> files = await FilePicker.getMultiFile( FilePickerResult result = await FilePicker.platform.pickFiles(
type: FileType.custom, type: FileType.custom,
allowedExtensions: ['jpg', 'pdf', 'doc'], allowedExtensions: ['jpg', 'pdf', 'doc'],
); );
``` ```
### Load result and file details
```
FilePickerResult result = await FilePicker.platform.pickFiles();
if(result != null) {
PlatformFile file = result.files.first;
print(file.name);
print(file.bytes);
print(file.size);
print(file.extension);
print(file.path);
}
```
For full usage details refer to the **[Wiki](https://github.com/miguelpruivo/flutter_file_picker/wiki)** above. For full usage details refer to the **[Wiki](https://github.com/miguelpruivo/flutter_file_picker/wiki)** above.
## Example App ## Example App

View File

@ -32,7 +32,9 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
_paths = (await FilePicker.platform.pickFiles( _paths = (await FilePicker.platform.pickFiles(
type: _pickingType, type: _pickingType,
allowMultiple: _multiPick, allowMultiple: _multiPick,
allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '')?.split(',') : null, allowedExtensions: (_extension?.isNotEmpty ?? false)
? _extension?.replaceAll(' ', '')?.split(',')
: null,
)) ))
?.files; ?.files;
} on PlatformException catch (e) { } on PlatformException catch (e) {
@ -52,7 +54,9 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
_scaffoldKey.currentState.showSnackBar( _scaffoldKey.currentState.showSnackBar(
SnackBar( SnackBar(
backgroundColor: result ? Colors.green : Colors.red, 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')),
), ),
); );
}); });
@ -124,7 +128,8 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
maxLength: 15, maxLength: 15,
autovalidate: true, autovalidate: true,
controller: _controller, controller: _controller,
decoration: InputDecoration(labelText: 'File extension'), decoration:
InputDecoration(labelText: 'File extension'),
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.none, textCapitalization: TextCapitalization.none,
) )
@ -133,8 +138,10 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints.tightFor(width: 200.0), constraints: const BoxConstraints.tightFor(width: 200.0),
child: SwitchListTile.adaptive( child: SwitchListTile.adaptive(
title: Text('Pick multiple files', textAlign: TextAlign.right), title:
onChanged: (bool value) => setState(() => _multiPick = value), Text('Pick multiple files', textAlign: TextAlign.right),
onChanged: (bool value) =>
setState(() => _multiPick = value),
value: _multiPick, value: _multiPick,
), ),
), ),
@ -171,15 +178,28 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
: _paths != null : _paths != null
? Container( ? Container(
padding: const EdgeInsets.only(bottom: 30.0), padding: const EdgeInsets.only(bottom: 30.0),
height: MediaQuery.of(context).size.height * 0.50, height:
MediaQuery.of(context).size.height * 0.50,
child: Scrollbar( child: Scrollbar(
child: ListView.separated( child: ListView.separated(
itemCount: _paths != null && _paths.isNotEmpty ? _paths.length : 1, itemCount:
itemBuilder: (BuildContext context, int index) { _paths != null && _paths.isNotEmpty
final bool isMultiPath = _paths != null && _paths.isNotEmpty; ? _paths.length
final String name = : 1,
'File $index: ' + (isMultiPath ? _paths.map((e) => e.name).toList()[index] : _fileName ?? '...'); itemBuilder:
final path = _paths.map((e) => e.path).toList()[index].toString(); (BuildContext context, int index) {
final bool isMultiPath =
_paths != null && _paths.isNotEmpty;
final String name = 'File $index: ' +
(isMultiPath
? _paths
.map((e) => e.name)
.toList()[index]
: _fileName ?? '...');
final path = _paths
.map((e) => e.path)
.toList()[index]
.toString();
return ListTile( return ListTile(
title: Text( title: Text(
@ -188,7 +208,9 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
subtitle: Text(path), subtitle: Text(path),
); );
}, },
separatorBuilder: (BuildContext context, int index) => const Divider(), separatorBuilder:
(BuildContext context, int index) =>
const Divider(),
)), )),
) )
: const SizedBox(), : const SizedBox(),

View File

@ -43,12 +43,20 @@ abstract class FilePicker extends PlatformInterface {
/// Retrieves the file(s) from the underlying platform /// Retrieves the file(s) from the underlying platform
/// ///
/// Default [type] set to `FileType.any` with [allowMultiple] set to `false` /// Default [type] set to [FileType.any] with [allowMultiple] set to [false]
/// Optionally, [allowedExtensions] might be provided (e.g. [`pdf`, `svg`, `jpg`].). /// Optionally, [allowedExtensions] might be provided (e.g. `[pdf, svg, jpg]`.).
///
/// If [withData] is set, picked files will have its byte data immediately available on memory as [Uint8List]
/// which can be useful if you are picking it for server upload or similar.
/// ///
/// If you want to track picking status, for example, because some files may take some time to be /// If you want to track picking status, for example, because some files may take some time to be
/// cached (particularly those picked from cloud providers), you may want to set [onFileLoading] handler /// cached (particularly those picked from cloud providers), you may want to set [onFileLoading] handler
/// that will give you the current status of picking. /// that will give you the current status of picking.
///
/// The result is wrapped in a [FilePickerResult] which contains helper getters
/// with useful information regarding the picked [List<PlatformFile>].
///
/// Returns [null] if aborted.
Future<FilePickerResult> pickFiles({ Future<FilePickerResult> pickFiles({
FileType type = FileType.any, FileType type = FileType.any,
List<String> allowedExtensions, List<String> allowedExtensions,
@ -65,12 +73,14 @@ abstract class FilePicker extends PlatformInterface {
/// each platform and it isn't required to invoke this as the system should take care /// each platform and it isn't required to invoke this as the system should take care
/// of it whenever needed. However, this will force the cleanup if you want to manage those on your own. /// of it whenever needed. However, this will force the cleanup if you want to manage those on your own.
/// ///
/// Returns `true` if the files were removed with success, `false` otherwise. /// Returns [true] if the files were removed with success, [false] otherwise.
Future<bool> clearTemporaryFiles() async => throw UnimplementedError('clearTemporaryFiles() has not been implemented.'); Future<bool> clearTemporaryFiles() async => throw UnimplementedError(
'clearTemporaryFiles() has not been implemented.');
/// Selects a directory and returns its absolute path. /// Selects a directory and returns its absolute path.
/// ///
/// On Android, this requires to be running on SDK 21 or above, else won't work. /// 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. /// Returns [null] if folder path couldn't be resolved.
Future<String> getDirectoryPath() async => throw UnimplementedError('getDirectoryPath() has not been implemented.'); Future<String> getDirectoryPath() async =>
throw UnimplementedError('getDirectoryPath() has not been implemented.');
} }

View File

@ -7,8 +7,10 @@ import 'package:flutter/services.dart';
import 'file_picker_result.dart'; import 'file_picker_result.dart';
const MethodChannel _channel = MethodChannel('miguelruivo.flutter.plugins.filepicker'); const MethodChannel _channel =
const EventChannel _eventChannel = EventChannel('miguelruivo.flutter.plugins.filepickerevent'); MethodChannel('miguelruivo.flutter.plugins.filepicker');
const EventChannel _eventChannel =
EventChannel('miguelruivo.flutter.plugins.filepickerevent');
/// An implementation of [FilePicker] that uses method channels. /// An implementation of [FilePicker] that uses method channels.
class FilePickerIO extends FilePicker { class FilePickerIO extends FilePicker {
@ -34,7 +36,8 @@ class FilePickerIO extends FilePicker {
); );
@override @override
Future<bool> clearTemporaryFiles() async => _channel.invokeMethod<bool>('clear'); Future<bool> clearTemporaryFiles() async =>
_channel.invokeMethod<bool>('clear');
@override @override
Future<String> getDirectoryPath() async { Future<String> getDirectoryPath() async {
@ -59,13 +62,16 @@ class FilePickerIO extends FilePicker {
) async { ) async {
final String type = describeEnum(fileType); final String type = describeEnum(fileType);
if (type != 'custom' && (allowedExtensions?.isNotEmpty ?? false)) { 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 { try {
_eventSubscription?.cancel(); _eventSubscription?.cancel();
if (onFileLoading != null) { if (onFileLoading != null) {
_eventSubscription = _eventChannel.receiveBroadcastStream().listen( _eventSubscription = _eventChannel.receiveBroadcastStream().listen(
(data) => onFileLoading((data as bool) ? FilePickerStatus.picking : FilePickerStatus.done), (data) => onFileLoading((data as bool)
? FilePickerStatus.picking
: FilePickerStatus.done),
onError: (error) => throw Exception(error), onError: (error) => throw Exception(error),
); );
} }
@ -81,12 +87,14 @@ class FilePickerIO extends FilePicker {
return null; return null;
} }
return FilePickerResult(result.map((file) => PlatformFile.fromMap(file)).toList()); return FilePickerResult(
result.map((file) => PlatformFile.fromMap(file)).toList());
} on PlatformException catch (e) { } on PlatformException catch (e) {
print('[$_tag] Platform exception: $e'); print('[$_tag] Platform exception: $e');
rethrow; rethrow;
} catch (e) { } 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; rethrow;
} }
} }

View File

@ -19,7 +19,10 @@ class FilePickerResult {
/// original files (which can be accessed through its URI property). /// original files (which can be accessed through its URI property).
/// ///
/// Only available on IO. Throws `UnsupportedError` on Web. /// Only available on IO. Throws `UnsupportedError` on Web.
List<String> get paths => files.map((file) => kIsWeb ? throw UnsupportedError('Unsupported on Web') : file.path).toList(); List<String> get paths => files
.map((file) =>
kIsWeb ? throw UnsupportedError('Unsupported on Web') : file.path)
.toList();
/// A `List<String>` containing all names from picked files with its extensions. /// A `List<String>` containing all names from picked files with its extensions.
List<String> get names => files.map((file) => file.name).toList(); List<String> get names => files.map((file) => file.name).toList();

View File

@ -26,7 +26,8 @@ class FilePickerWeb extends FilePicker {
bool allowCompression, bool allowCompression,
bool withData = true, bool withData = true,
}) async { }) async {
final Completer<List<PlatformFile>> filesCompleter = Completer<List<PlatformFile>>(); final Completer<List<PlatformFile>> filesCompleter =
Completer<List<PlatformFile>>();
String accept = _fileType(type, allowedExtensions); String accept = _fileType(type, allowedExtensions);
html.InputElement uploadInput = html.FileUploadInputElement(); html.InputElement uploadInput = html.FileUploadInputElement();
@ -42,7 +43,8 @@ class FilePickerWeb extends FilePicker {
List<PlatformFile> pickedFiles = []; List<PlatformFile> pickedFiles = [];
reader.onLoadEnd.listen((e) { reader.onLoadEnd.listen((e) {
final Uint8List bytes = Base64Decoder().convert(reader.result.toString().split(",").last); final Uint8List bytes =
Base64Decoder().convert(reader.result.toString().split(",").last);
pickedFiles.add( pickedFiles.add(
PlatformFile( PlatformFile(
@ -83,7 +85,8 @@ class FilePickerWeb extends FilePicker {
return 'video/*|image/*'; return 'video/*|image/*';
case FileType.custom: case FileType.custom:
return allowedExtensions.fold('', (prev, next) => '${prev.isEmpty ? '' : '$prev,'} .$next'); return allowedExtensions.fold(
'', (prev, next) => '${prev.isEmpty ? '' : '$prev,'} .$next');
break; break;
} }
return ''; return '';

View File

@ -6,15 +6,13 @@ class PlatformFile {
this.name, this.name,
this.bytes, this.bytes,
this.size, this.size,
this.isDirectory = false,
}); });
PlatformFile.fromMap(Map data) PlatformFile.fromMap(Map data)
: this.path = data['path'], : this.path = data['path'],
this.name = data['name'], this.name = data['name'],
this.bytes = data['bytes'], this.bytes = data['bytes'],
this.size = data['size'], this.size = data['size'];
this.isDirectory = data['isDirectory'];
/// The absolute path for a cached copy of this file. It can be used to create a /// The absolute path for a cached copy of this file. It can be used to create a
/// a file instance with a descriptor for the given path. /// a file instance with a descriptor for the given path.
@ -33,9 +31,6 @@ class PlatformFile {
/// The file size in KB. /// The file size in KB.
final int size; final int size;
/// Whether this file references a directory or not.
final bool isDirectory;
/// File extension for this file. /// File extension for this file.
String get extension => path?.split('.')?.last; String get extension => path?.split('.')?.last;
} }