Skip to content

Commit

Permalink
Merge pull request #47 from miguelpruivo/beta
Browse files Browse the repository at this point in the history
beta (1.3.0) > master (1.2.0)
  • Loading branch information
Miguel Ruivo authored Mar 12, 2019
2 parents 52756b1 + 9377775 commit d026ae9
Show file tree
Hide file tree
Showing 18 changed files with 545 additions and 285 deletions.
18 changes: 16 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
## 1.3.0

**Breaking changes**
* `FileType.CAMERA` is no longer available, if you need it, you can use this package along with [image_picker](https://pub.dartlang.org/packages/image_picker).

**New features**
* You can now pick multiple files by using the `getMultiFilePath()` method which will return a `Map<String,String>` with all paths from selected files, where the key matches the file name and the value its path. Optionally, it also supports filtering by file extension, otherwise all files will be selectable. Nevertheless, you should keep using `getFilePath()` for single path picking.
* You can now use `FileType.AUDIO` to pick audio files. In iOS this will let you select from your music library. Paths from DRM protected files won't be loaded (see README for more details).
* Adds `getFile()` utility method that does the same of `getFilePath()` but returns a `File` object instead, for the returned path.

**Bug fixes and updates**
* This package is no longer attached to the [image_picker](https://pub.dartlang.org/packages/image_picker), and because of that, camera permission is also no longer required.
* Fixes an issue where sometimes the _InputStream_ wasn't being properly closed. Also, its exception is now being forward to the plugin caller.
* Fixes an issue where the picker, when canceled, wasn't calling the result callback on the underlying platforms.

## 1.2.0

**Breaking change**
Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library.
**Breaking change**: Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library.

## 1.1.1

Expand Down
148 changes: 83 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,100 +3,118 @@

# file_picker

File picker plugin alows you to use a native file explorer to load absolute file path from different file types.
A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extensions filtering support.

## Installation

First, add *file_picker* as a dependency in [your pubspec.yaml file](https://flutter.io/platform-plugins/).

```
file_picker: ^1.2.0
file_picker: ^1.3.0
```
## Android
Add `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>` to your app `AndroidManifest.xml` file.
### Android
Add `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>` to your app `AndroidManifest.xml` file. This is required due to file caching when a path is required from a remote file (eg. Google Drive).

## iOS
Since we are using *image_picker* as a dependency from this plugin to load paths from gallery and camera, we need the following keys to your _Info.plist_ file, located in `<project root>/ios/Runner/Info.plist`:
### iOS
Based on the location of the files that you are willing to pick paths, you may need to add some keys to your iOS app's _Info.plist_ file, located in `<project root>/ios/Runner/Info.plist`:

* **_UIBackgroundModes_** with the **_fetch_** and **_remote-notifications_** keys - Required if you'll be using the `FileType.ANY` or `FileType.CUSTOM`. Describe why your app needs to access background taks, such downloading files (from cloud services). This is called _Required background modes_, with the keys _App download content from network_ and _App downloads content in response to push notifications_ respectively in the visual editor (since both methods aren't actually overriden, not adding this property/keys may only display a warning, but shouldn't prevent its correct usage).

```
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
```

* **_NSAppleMusicUsageDescription_** - Required if you'll be using the `FileType.AUDIO`. Describe why your app needs permission to access music library. This is called _Privacy - Media Library Usage Description_ in the visual editor.

```
<key>NSAppleMusicUsageDescription</key>
<string>Explain why your app uses music</string>
```


* **_NSPhotoLibraryUsageDescription_** - Required if you'll be using the `FileType.IMAGE` or `FileType.VIDEO`. Describe why your app needs permission for the photo library. This is called _Privacy - Photo Library Usage Description_ in the visual editor.

```
<key>NSPhotoLibraryUsageDescription</key>
<string>Explain why your app uses photo library</string>
```

**Note:** Any iOS version below 11.0, will require an Apple Developer Program account to enable _CloudKit_ and make it possible to use the document picker (which happens when you select `FileType.ALL`, `FileType.CUSTOM` or any other option with `getMultiFilePath()`). You can read more about it [here]( https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitQuickStart/EnablingiCloudandConfiguringCloudKit/EnablingiCloudandConfiguringCloudKit.html).

* `NSPhotoLibraryUsageDescription` - describe why your app needs permission for the photo library. This is called _Privacy - Photo Library Usage Description_ in the visual editor.
* `NSCameraUsageDescription` - describe why your app needs access to the camera. This is called _Privacy - Camera Usage Description_ in the visual editor.
* `NSMicrophoneUsageDescription` - describe why your app needs access to the microphone, if you intend to record videos. This is called _Privacy - Microphone Usage Description_ in the visual editor.
* `UIBackgroundModes` with the `fetch` and `remote-notifications` keys - describe why your app needs to access background taks, such downloading files (from cloud services) when not cached to locate path. This is called _Required background modes_, with the keys _App download content from network_ and _App downloads content in response to push notifications_ respectively in the visual editor (since both methods aren't actually overriden, not adding this property/keys may only display a warning, but shouldn't prevent its correct usage).

## Usage
There's only one method within this package
`FilePicker.getFilePath()`
this receives 2 optional parameters, the `fileType` and a `fileExtension` to be used along with `FileType.CUSTOM`.
So, 2 basically usages may be:
There are only two methods that should be used with this package:

#### `FilePicker.getFilePath()`

Will let you pick a **single** file. This receives two optional parameters: the `fileType` for specifying the type of the picker and a `fileExtension` parameter to filter selectable files. The available filters are:
* `FileType.ANY` - Will let you pick all available files.
* `FileType.CUSTOM` - Will let you pick a single path for the extension matching the `fileExtension` provided.
* `FileType.IMAGE` - Will let you pick a single image file. Opens gallery on iOS.
* `FileType.VIDEO` - WIll let you pick a single video file. Opens gallery on iOS.
* `FileType.AUDIO` - Will let you pick a single audio file. Opens music on iOS. Note that DRM protected files won't provide a path, `null` will be returned instead.

#### `FilePicker.getMultiFilePath()`

Will let you select **multiple** files and retrieve its path at once. Optionally you can provide a `fileExtension` parameter to filter the allowed selectable files.
Will return a `Map<String,String>` with the files name (`key`) and corresponding path (`value`) of all selected files.
Picking multiple paths from iOS gallery (image and video) aren't currently supported.

#### Usages

So, a few example usages can be as follow:
```
await FilePicker.getFilePath(type: FileType.ANY); // will display all file types
await FilePicker.getFilePath(type: FileType.CUSTOM, fileExtension: 'svg'); // will filter and display only files with SVG extension.
// Single file path
String filePath;
filePath = await FilePicker.getFilePath(type: FileType.ANY); // will let you pick one file path, from all extensions
filePath = await FilePicker.getFilePath(type: FileType.CUSTOM, fileExtension: 'svg'); // will filter and only let you pick files with svg extension
// Pick a single file directly
File file = await FilePicker.getFile(type: FileType.ANY); // will return a File object directly from the selected file
// Multi file path
Map<String,String> filesPaths;
filePaths = await FilePicker.getMultiFilePath(); // will let you pick multiple files of any format at once
filePaths = await FilePicker.getMultiFilePath(fileExtension: 'pdf'); // will let you pick multiple pdf files at once
filePaths = await FilePicker.getMultiFilePath(type: FileType.IMAGE); // will let you pick multiple image files at once
List<String> allNames = filePaths.keys; // List of all file names
List<String> allPaths = filePaths.values; // List of all paths
String someFilePath = filePaths['fileName']; // Access a file path directly by its name (matching a key)
```

**Note:** When using `FileType.CUSTOM`, unsupported extensions will throw a `MissingPluginException` that is handled by the plugin.
##### A few side notes
* Using `getMultiFilePath()` on iOS will always use the document picker (aka Files app). This means that multi picks are not currently supported for photo library images/videos or music library files.
* When using `FileType.CUSTOM`, unsupported extensions will throw a `MissingPluginException` that is handled by the plugin.
* On Android, when available, you should avoid using third-party file explorers as those may prevent file extension filtering (behaving as `FileType.ANY`). In this scenario, you will need to validate it on return.

## Currently supported features
* [X] Load paths from **cloud files** (GDrive, Dropbox, iCloud)
* [X] Load path from a **custom format** by providing a file extension (pdf, svg, zip, etc.)
* [X] Load path from **multiple files** optionally, supplying a file extension
* [X] Load path from **gallery**
* [X] Load path from **camera**
* [X] Load path from **audio**
* [X] Load path from **video**
* [X] Load path from **any** type of file (without filtering)
* [X] Load path from a **custom format** by providing a file extension (pdf, svg, zip, etc.)
* [X] Load path from **any**
* [X] Create a `File` object from **any** selected file

## Demo App

![Demo](https://github.com/miguelpruivo/plugins_flutter_file_picker/blob/master/example/example.gif)

## Example
```
import 'package:file_picker/file_picker.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _filePath;
void getFilePath() async {
try {
String filePath = await FilePicker.getFilePath(type: FileType.ANY);
if (filePath == '') {
return;
}
print("File path: " + filePath);
setState((){this._filePath = filePath;});
} on PlatformException catch (e) {
print("Error while picking the file: " + e.toString());
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('File Picker Example'),
),
body: new Center(
child: _filePath == null
? new Text('No file selected.')
: new Text('Path' + _filePath),
),
floatingActionButton: new FloatingActionButton(
onPressed: getFilePath,
tooltip: 'Select file',
child: new Icon(Icons.sd_storage),
),
);
}
}
```
See example app.

## Getting Started

For help getting started with Flutter, view our online
[documentation](https://flutter.io/).

For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code).



Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
import android.util.Log;
import android.webkit.MimeTypeMap;

import java.io.BufferedOutputStream;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;


import io.flutter.plugin.common.MethodCall;
Expand All @@ -36,6 +35,7 @@ public class FilePickerPlugin implements MethodCallHandler {
private static Result result;
private static Registrar instance;
private static String fileType;
private static boolean isMultipleSelection = false;

/** Plugin registration. */
public static void registerWith(Registrar registrar) {
Expand All @@ -49,48 +49,40 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {

if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {

if (data != null) {
if(data.getClipData() != null) {
int count = data.getClipData().getItemCount();
int currentItem = 0;
ArrayList<String> paths = new ArrayList<>();
while(currentItem < count) {
final Uri currentUri = data.getClipData().getItemAt(currentItem).getUri();
String path = FileUtils.getPath(currentUri, instance.context());
paths.add(path);
Log.i(TAG, "[MultiFilePick] File #" + currentItem + " - URI: " +currentUri.getPath());
currentItem++;
}
result.success(paths);
} else if (data != null) {
Uri uri = data.getData();
Log.i(TAG, "URI:" +data.getData().toString());
Log.i(TAG, "[SingleFilePick] File URI:" +data.getData().toString());
String fullPath = FileUtils.getPath(uri, instance.context());
String cloudFile = null;

if(fullPath == null)
{
FileOutputStream fos = null;
cloudFile = instance.activeContext().getCacheDir().getAbsolutePath() + "/" + FileUtils.getFileName(uri, instance.activeContext());

try {
fos = new FileOutputStream(cloudFile);
try{
BufferedOutputStream out = new BufferedOutputStream(fos);
InputStream in = instance.activeContext().getContentResolver().openInputStream(uri);

byte[] buffer = new byte[8192];
int len = 0;

while ((len = in.read(buffer)) >= 0){
out.write(buffer, 0, len);
}

out.flush();
} finally {
fos.getFD().sync();
}

} catch (Exception e) {
e.printStackTrace();
}

Log.i(TAG, "Cloud file loaded and cached on:" + cloudFile);
fullPath = cloudFile;
if(fullPath == null) {
fullPath = FileUtils.getUriFromRemote(instance.activeContext(), uri, result);
}

Log.i(TAG, "Absolute file path:" + fullPath);
result.success(fullPath);
if(fullPath != null) {
Log.i(TAG, "Absolute file path:" + fullPath);
result.success(fullPath);
} else {
result.error(TAG, "Failed to retrieve path." ,null);
}
}

return true;
} else if(requestCode == REQUEST_CODE && resultCode == Activity.RESULT_CANCELED) {
result.success(null);
return true;
}
result.error(TAG, "Unknown activity error, please fill an issue." ,null);
return false;
}
});
Expand All @@ -112,9 +104,12 @@ public boolean onRequestPermissionsResult(int requestCode, String[] strings, int
public void onMethodCall(MethodCall call, Result result) {
this.result = result;
fileType = resolveType(call.method);
isMultipleSelection = (boolean)call.arguments;

if(fileType == null){
if(fileType == null) {
result.notImplemented();
} else if(fileType.equals("unsupported")) {
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 {
startFileExplorer(fileType);
}
Expand All @@ -128,7 +123,6 @@ private static boolean checkPermission() {
}

private static void requestPermission() {

Activity activity = instance.activity();
Log.i(TAG, "Requesting permission: " + permission);
String[] perm = { permission };
Expand All @@ -142,13 +136,16 @@ private String resolveType(String type) {
if(isCustom) {
final String extension = type.split("__CUSTOM_")[1].toLowerCase();
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
mime = mime == null ? "unsupported" : mime;
Log.i(TAG, "Custom file type: " + mime);
return mime;
}

switch (type) {
case "PDF":
return "application/pdf";
case "AUDIO":
return "audio/*";
case "IMAGE":
return "image/*";
case "VIDEO":
return "video/*";
case "ANY":
Expand All @@ -174,10 +171,9 @@ private static void startFileExplorer(String type) {
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + File.separator);
intent.setDataAndType(uri, type);
intent.setType(type);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultipleSelection);
intent.addCategory(Intent.CATEGORY_OPENABLE);

Log.d(TAG, "Intent: " + intent.toString());

instance.activity().startActivityForResult(intent, REQUEST_CODE);
} else {
requestPermission();
Expand Down
Loading

0 comments on commit d026ae9

Please sign in to comment.