Word from creator
Hello👋, This package is completely compatible with flutter and it also provides option to disable copying of file in cache when picking and provide Android Uri of picked file to work with which offer some real benifits such as getting original file metadata, filtering files before caching or caching them anytime later using Uri.
Yes, without a doubt, giving a free 👍 or ⭐ will encourage me to keep working on this plugin.
Package description
A Flutter file picking and saving package that enables you to pick or save a single file and multiple files.
Features
- Works on Android 5.0 (API level 21) or later.
- Pick single file, multiple files with certain extensions or mime types.
- Supports photo picker on supported devices.
- Supports picking directory with persistent permission and traversing the picked directory documents.
- Get meta data like name, size and last modified from from android uri or file path.
- Saves single file while allowing user to choose location and name.
- Saves multiple file while allowing user to choose location or directory for saving all files.
- Saves file from either file path or file data(Uint8List).
- Could limit picking a file to the local device only.
- Get cached file path from android uri or file path.
Note: If you are getting errors in you IDE after updating this plugin to newer version and the error contains works like Redeclaration, Conflicting declarations, Overload resolution ambiguity then to fix that you probably need to remove the older version of plugin from pub cache C:\Users\username\AppData\Local\Pub\Cache\hosted\pub.dev\older_version
or simply run flutter clean
.
Getting started
- In pubspec.yaml, add this dependency:
pick_or_save:
- Add this package to your project:
import 'package:pick_or_save/pick_or_save.dart';
Basic Usage
Note: To try the demos shown in below gifs run the example included in this plugin.
Note: For most below examples we set getCachedFilePath = false
to get uri path instead of absolute file path from picker. A Uri path can only be used in android native code. By default getCachedFilePath = true
which will provide cached file path from picker.
Picking
Picking single file | Picking multiple files |
---|---|
Picking single file and getting uri
List<String>? result = await PickOrSave().filePicker(
params: FilePickerParams(getCachedFilePath: false),
);
String filePath = result[0];
Picking single file and getting cache path
List<String>? result = await PickOrSave().filePicker(
params: FilePickerParams(getCachedFilePath: true),
);
String filePath = result[0];
Note:-
If getCachedFilePath = true
then the returned path file name will be different from picked file name. This was done to avoid deleting or rewriting existing cache files with same name. But you can still get the original name by following the pattern.
For example:- If you pick a file with name "My Test File.pdf" then the cached file will be something like this "My Test File.8190480413118007032.pdf". From that we see the pattern would be "original name prefix"+"."+"random numbers"+"."+"file extension". So what we need to do is to just remove the "."+"random numbers" to get the real name. Look at the below code to do that:
String getRealName(String pickOrSaveCachedFileName) {
int indexOfExtDot = pickOrSaveCachedFileName.lastIndexOf('.');
if (indexOfExtDot == -1) {
return pickOrSaveCachedFileName;
} else {
String fileExt =
pickOrSaveCachedFileName.substring(indexOfExtDot).toLowerCase();
String fileNameWithoutExtension = pickOrSaveCachedFileName.substring(
0, pickOrSaveCachedFileName.length - fileExt.length);
int indexOfRandomNumDot = fileNameWithoutExtension.lastIndexOf('.');
if (indexOfRandomNumDot == -1) {
return pickOrSaveCachedFileName;
} else {
String dotAndRandomNum =
fileNameWithoutExtension.substring(indexOfRandomNumDot).toLowerCase();
String fileNameWithoutDotAndRandomNumAndExtension =
fileNameWithoutExtension.substring(
0, fileNameWithoutExtension.length - dotAndRandomNum.length);
return fileNameWithoutDotAndRandomNumAndExtension + fileExt;
}
}
}
Picking multiple files
List<String>? filesPaths = await PickOrSave().filePicker(
params: FilePickerParams(getCachedFilePath: false, enableMultipleSelection: true),
);
Resticting picking files to certain mime types
List<String>? filesPaths = await PickOrSave().filePicker(
params: FilePickerParams(getCachedFilePath: false, mimeTypesFilter: ["image/*", "application/pdf"]),
);
Resticting picking files to certain extensions
List<String>? filesPaths = await PickOrSave().filePicker(
params: FilePickerParams(getCachedFilePath: false, allowedExtensions: [".txt", ".png"]),
);
Note: This plugin automatically tries to convert the extensions to their respective mime types if supported so that only those become selectable but that may fail if it fails to convert them. Still if a user manages to select other extension files then this plugin automatically discards those other extension files from selection.
Photo Picker
List<String>? filesPaths = await PickOrSave().filePicker(
params: FilePickerParams(getCachedFilePath: false, pickerType: PickerType.photo, mimeTypesFilter: ["*/*"]),
);
Note: This will show new photo picker only on supported android devices and for unsupported android devices it will show default picker. And it always needs mime type and only first mime type in mimeTypesFilter list is used. So if you want to filter multiple types of files then make sure to provide allowedExtensions
as that automatically discards other extension files from selection if selected by user.
Photo picker on supported devices | Photo picker on unsupported devices |
---|---|
Picking Directory
String? pickedDirectoryUri = await PickOrSave().directoryPicker(
params: DirectoryPickerParams()
);
The obtained uri will have persistent permissions with these flags: Intent.FLAG_GRANT_READ_URI_PERMISSION, Intent.FLAG_GRANT_WRITE_URI_PERMISSION. In short it preserves access to uri across device restarts. You can learn more about it here.
Note: In DirectoryPickerParams()
you can also optionally provide initialDirectoryUri
which will be used to start the directory picker from a speific location. Generally we give it the uri which we stored from previous directory pickings.
Also, For traversing the picked directory, releasing-checking persistent permissions for a uri then go here.
Saving
Saving single file from file path
List<String>? result = await PickOrSave().fileSaver(
params: FileSaverParams(
saveFiles: [
SaveFileInfo(
filePath: filePath,
fileName: "File.png")
],
)
);
String savedFilePath = result[0];
Saving multiple files from file path
List<String>? result = await PickOrSave().fileSaver(
params: FileSaverParams(
saveFiles: [
SaveFileInfo(
filePath: filePath,
fileName: "File 1.png"),
SaveFileInfo(
filePath: filePath,
fileName: "File 2.png")
],
)
);
Saving multiple files from Uint8List
List<String>? result = await PickOrSave().fileSaver(
params: FileSaverParams(
saveFiles: [
SaveFileInfo(
fileData: uint8List,
fileName: "File 1.png"),
SaveFileInfo(
fileData: uint8List,
fileName: "File 2.png")
],
)
);
Saving single file | Saving multiple files |
---|---|
File Metadata
FileMetadata? result = await PickOrSave().fileMetaData(
params: FileMetadataParams(filePath: filePath),
);
Picking file and get its metadata |
---|
Get cache file path from file Uri or absolute file path
String? result = await PickOrSave().cacheFilePathFromPath(
params: CacheFilePathFromPathParams(filePath: filePath),
);
Picking file and get its cached file path |
---|
Operations on picked directory uri
Get uris of documents or sub documents inside a picked directory
List<DocumentFile>? documentFiles = await PickOrSave().directoryDocumentsPicker(
params: DirectoryDocumentsPickerParams(
directoryUri: pickedDirectoryUri,
recurseDirectories: false,
allowedExtensions: [".pdf"],
mimeTypesFilter: ["image/*"],
),
);
DocumentFile documentFile = documentFiles[0];
String documentId = documentFile.id;
String documentUri = documentFile.uri;
String? documentMimeType = documentFile.mimeType;
String documentName = documentFile.name;
bool isDocumentDirectory = documentFile.isDirectory;
bool isDocumentFile = documentFile.isFile;
DirectoryDocumentsPickerParams
can take these parameters:
- Provide
directoryUri
the picked directory uri or uri of documents(sub directory) inside picked directory obtained from previous runs. - Provide
documentId
the id of documents(sub directory) inside picked directory obtained from previous runs. This is important if you want to start traversing from a sub directory instead of root directory. - Set
recurseDirectories
to true if you want to traverse inside sub directories of provided directory. This is recursive. - Provide
allowedExtensions
to filter the returned documents to certain file extensions. It has no effect on performance. - Provide
mimeTypesFilter
to filter the returned documents to certain mime types. It has no effect on performance.
Cancelling traversing a picked directory
String? result = await PickOrSave().cancelActions(
params: CancelActionsParams(cancelType: CancelType.directoryDocumentsPicker),
);
Cancelling traversing a picked directory
String? result = await PickOrSave().cancelActions(
params: CancelActionsParams(cancelType: CancelType.directoryDocumentsPicker),
);
Check if a uri has persistent permission
bool? persistentPermStatus = await PickOrSave().uriPermissionStatus(
params: UriPermissionStatusParams(uri: uriToCheck, releasePermission: false),
);
Get all uri which have persistent permission
List<String>? persistentPermUris = await PickOrSave().urisWithPersistedPermission();
Remove or relase persistent permission for a uri
bool? persistentPermStatus = await PickOrSave().uriPermissionStatus(
params: UriPermissionStatusParams(uri: uriToRelease, releasePermission: true),
);
Note: It is very important to remove unused persited uris as android tracks uri permissions individual apps for setting a hardcoded limit to it. Till Android 10 the limit is set to 128 persisted permission grants and from Android 11 that limit updated to 512. So, if we reach that limit then future requests will get failed. For more details follow the nice article here.