Skip to content

chaudharydeepanshu/pick_or_save

Repository files navigation

pub package wakatime

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.