docman 1.0.1
docman: ^1.0.1 copied to clipboard
Flutter File & Directory Plugin. Easy Android file operations with Storage Access Framework (SAF) API integration.
Document Manager (DocMan) #
A Flutter plugin that simplifies file & directory operations on Android devices. Leveraging the Storage Access Framework (SAF) API, it provides seamless integration for files & directories operations, persisted permissions management, and more.
π Features #
- Picker for files & directories.
- App directories path retriever (cache, files, external storage cache & files).
- Manage files & directories (create, delete, list, copy, move, open, save, share, etc.).
- Thumbnail generation for images, videos, pdfs.
- Stream-based file reading, directory listing.
- Separated actions that can be performed in the background, like with isolates or WorkManager.
- Persisted permissions management.
- No manifest permissions are required.
π€ Supported Android versions #
- Android 5.0 (API level 21) and above.
Example usage #
import 'package:docman/docman.dart';
///Get the app's internal cache directory
/// Path Example: `/data/user/0/devdf.plugins.docman_example/cache`
Future<Directory?> getCachedDir() => DocMan.dir.cache();
///Pick a directory
Future<DocumentFile?> pickDir() => DocMan.pick.directory();
///Pick files & copy them to cache directory
Future<List<File>> pickFiles() => DocMan.pick.files(extensions: ['pdf', '.doc', 'docx']);
///Pick visual media (images & videos) - uses Android Photo Picker if available
Future<List<File>> pickMedia() => DocMan.pick.visualMedia(limit: 5, limitResultRestart: true);
///Get list of persisted permissions (directories only)
Future<List<Permission>> getPermissions() async => await DocMan.perms.list(files: false);
///DocumentFile used for file & directory operations
Future<void> dirOperationsExample() async {
//Instantiate a DocumentFile from saved URI
final dir = await DocumentFile(uri: 'content://com.android.externalstorage.documents/document/primary%3ADocMan')
.get();
//List directory files with mimeTypes filter
final List<DocumentFile> documents = await dir.listDocuments(mimeTypes: ['application/pdf']);
}
///And more... Check the documentation for more details.
π Documentation #
API documentation is available at pub.flutter-io.cn. All public classes and methods are well-documented.
Note: To try the demos shown in the images run the example included in this plugin.
Table of Contents #
- π οΈ Installation
- π Picker (πΌοΈ see examples)
- π App Directories (πΌοΈ see examples)
- π‘οΈ Persisted permissions (πΌοΈ see examples)
- π DocumentFile (πΌοΈ see examples)
- ποΈ DocMan Exceptions
- π¦ Changelog
- βοΈ Help & Questions
- π± Contributing
π οΈ Installation #
Add the following dependency to your pubspec.yaml file:
dependencies:
docman: ^1.0.0
Then run β‘οΈ flutter pub get.
π Picker #
The picker provides a simple way to select files and directories from the device storage.
The Class DocManPicker or helper: DocMan.pick provides the
following methods:
Pick directory
Allows picking a directory from the device storage. You can specify the initial directory to start from.
β οΈ When picking directory, it also grants access to it. On Android 11 (API level 30) and higher it's impossible to grant access to root directories of sdCard, download folder, also it's impossible to select any file from: Android/data/ directory and all subdirectories, Android/obb/ directory and all subdirectories.
All restrictions are described here at developer.android.com
β οΈ
initDir: Option to set initial directory uri for picker is available since Android 8.0 (Api 26). If the option is not available, the picker will start from the default directory.
Future<DocumentFile?> pickBackupDir() => DocMan.pick.directory(initDir: 'content uri to start from');
Pick documents
Allows picking single or multiple documents. You can specify the initial directory to start from. Filter by MIME types & extensions, by location - only local files (no cloud providers etc.) or both. You can choose a limit strategy when picking multiple documents. Grant persisted permissions to the picked documents.
Future<List<DocumentFile>> pickDocuments() =>
DocMan.pick.documents(
initDir: ' content uri to start from',
mimeTypes: ['application/pdf'],
extensions: ['pdf', '.docx'],
localOnly: true,
grantPermissions: true,
limit: 5,
limitResultRestart: true,
limitRestartToastText: 'Pick maximum 5 items',
);
Pick files
Allows picking single or multiple files. The difference from Pick documents
is that it returns a list of File(s) saved in the cache directory.
First, it will try to copy to the external cache directory; if not available, then to the internal cache directory.
You can specify the initial directory to start from, filter by MIME types & extensions, by location - show only local files (no cloud providers etc.). You can choose a limit strategy when picking multiple documents.
Future<List<File>> pickFiles() =>
DocMan.pick.files(
initDir: 'content uri to start from',
mimeTypes: ['application/pdf', 'application/msword'],
extensions: ['pdf', '.doc', 'txt'],
localOnly: false,
//Set limit to 1 for single file picking
limit: 5,
limitResultCancel: true, //cancel with exception picking if limit is exceeded
);
Pick visualMedia
This is almost the same as Pick files. Allows picking visual media like images or videos. It uses the Android Photo Picker (VisualMediaPicker) if available. You can disable the visual media picker if needed. You can specify the initial directory to start from, filter by MIME types & extensions, by location - show only local files (no cloud providers etc.). You can choose a limit strategy when picking multiple documents. Allows setting image quality for compression. All picked files will be copied to the cache directory (external or internal).
Future<List<File>> pickVisualMedia() =>
DocMan.pick.visualMedia(
initDir: 'content uri to start from',
mimeTypes: ['image/*'],
extensions: ['jpg', '.png', 'webp'],
// used only for images, default is 100
imageQuality: 70,
//fallback to default file picker if visual media picker is not available
useVisualMediaPicker: true,
localOnly: true,
limit: 3,
//Android PhotoPicker has limit functionality, system file picker has limit 100
limitResultEmpty: true, //return empty list if limit is exceeded
);
πΌοΈ Picker examples (click for expand/collapse)
| Picking directory | Picking documents |
|---|---|
| Picking files | Picking visualMedia |
|---|---|
π App Directories #
The plugin provides a way to get the app's internal & external directories,
like cache, files, data, external cache, external files, etc.
You can instantiate a DocManAppDirs class
or use the helper: DocMan.dir to get the directories.
Supported app directories
/// Get Application internal Cache Directory.
/// Path Example: `/data/user/0/devdf.plugins.docman_example/cache`
Future<Directory?> cache() => DocMan.dir.cache();
/// Get Application Files Directory.
/// The directory for storing files, rarely used.
/// Path Example: `/data/user/0/devdf.plugins.docman_example/files`
Future<Directory?> files() => DocMan.dir.files();
/// Get Application Data Directory.
/// Default Directory for storing data files of the app.
/// Path Example: `/data/user/0/devdf.plugins.docman_example/app_flutter`
Future<Directory?> data() => DocMan.dir.data();
/// Get Application External Cache Directory.
/// Path Example: `/storage/emulated/0/Android/data/devdf.plugins.docman_example/cache`
Future<Directory?> externalCache() => DocMan.dir.externalCache();
/// Get Application External Files Directory.
/// Path Example: `/storage/emulated/0/Android/data/devdf.plugins.docman_example/files`
Future<Directory?> filesExt() => DocMan.dir.filesExt();
β»οΈ Plugin Cache cleaner
During the app lifecycle, the cache (external or internal) directory can be filled with temporary files,
created by the plugin. When you pick files, visual media, or copy to cache, for example,
the plugin will create temporary files in the cache (external or internal) directory
in subdirectories like docManMedia and docMan.
To clean those directories, you can use the following method:
/// Clear Temporary Cache Directories.
///
/// Clears only the temp directories created by the plugin like `docManMedia` and `docMan`
/// in external & internal cache directories if exists.
///
/// Returns `true` if the directories were cleared successfully; otherwise, `false`.
Future<bool> clearPluginCache() => DocMan.dir.clearCache();
πΌοΈ App Directories examples (click for expand/collapse)
| Get directories |
|---|
π‘οΈ Persisted permissions #
DocMan provides a way to manage persisted permissions for directories & documents.
When you pick a directory or document with the parameter grantPermissions set to true,
its content URI gets a persistable permission grant.
Once taken, the permission grant will be remembered across device reboots.
If the grant has already been persisted, taking it again will just update the grant time.
You can instantiate a DocManPermissionManager class
or use the helper: DocMan.perms to manage permissions.
β οΈ Persistable permissions have limitations:
- Limited to 128 permissions per app for Android 10 and below
- Limited to 512 permissions per app for Android 11 and above
PersistedPermission class
PersistedPermission is a data class that holds information about the permission grant.
It is a representation of
the UriPermission Android class on Dart
side. It stores the uri and time of the permission grant, and whether it has read or write
access.
final perm = PersistedPermission(
uri: 'content://com.android.externalstorage.documents/tree/primary%3ADocMan',
read: true,
write: true,
time: 1733260689869);
List / Stream permissions
You can list or stream all persisted permissions. Also, you can filter permissions by files or directories or both.
/// Get list of all persisted permissions.
/// Optionally filter by files or directories.
Future<List<PersistedPermission>> listPerms({bool files = true, bool dirs = true}) =>
DocMan.perms.list(files: files, directories: dirs);
/// Stream all persisted permissions.
/// Optionally filter by files or directories.
Future<void> streamPerms() async {
final Stream<PersistedPermission> stream = DocMan.perms.listStream(files: false);
int countPerms = 0;
stream.listen((perm) {
countPerms++;
print(perm.toString());
}, onDone: () {
print('Stream Done, $countPerms permissions');
}, onError: (e) {
print('Error: $e');
});
}
List / Stream Documents with permissions
You can list or stream all documents (DocumentFile) with persisted permissions.
Also, you can filter documents by files or directories or both.
This method also removes the persisted permissions for the files/directories that no longer exist
(for example, the user deleted the file, through another app).
/// List all DocumentFiles with persisted permissions.
/// Optionally filter by files or directories.
Future<List<DocumentFile>> listDocumentsWithPerms({bool files = true, bool dirs = true}) =>
DocMan.perms.listDocuments(files: files, directories: dirs);
/// Stream all DocumentFiles with persisted permissions.
/// Optionally filter by files or directories.
Future<void> streamDocs() async {
final Stream<DocumentFile> stream = DocMan.perms.listDocumentsStream(directories: true, files: false);
int countDocs = 0;
stream.listen((doc) {
countDocs++;
print(doc.toString());
}, onDone: () {
print('Stream Done, $countDocs documents');
}, onError: (e) {
print('Error: $e');
});
}
Release & Release all actions
You can release a single permission for a specific URI or all permissions.
/// Release persisted permission for specific URI.
Future<bool> releasePermission(String uri) => DocMan.perms.release(uri);
/// PersistedPermission class has helper method to release permission.
Future<void> permAction() async {
final PersistedPermission perm = await DocMan.perms
.list()
.first;
await perm.release();
}
/// Release all persisted permissions.
Future<bool> releaseAllPermissions() => DocMan.perms.releaseAll();
Get Uri permission status
You can check if the URI has a persisted permission grant.
/// Check if URI has persisted permission grant.
Future<PersistedPermission?> hasPermission() =>
DocMan.perms.status('content://com.android.externalstorage.documents/tree/primary%3ADocMan');
β»οΈ Validate permissions
You can validate persisted permissions for files or directories. It will check each uri in persisted permissions list and remove invalid permissions (for example, the user deleted the file/directory through system file manager).
/// Validate the persisted permissions list.
/// Returns `true` if the list was validated successfully, otherwise throws an error.
Future<bool> validatePermissions() => DocMan.perms.validateList();
πΌοΈ Persisted permissions examples (click for expand/collapse)
| List/Stream Permissions | List/Stream Documents |
|---|---|
π DocumentFile #
DocumentFile is a class that represents a file or directory in the device storage.
It's a dart representation of
the android DocumentFile.
It provides methods to perform file & directory operations like create, delete, list, copy, open, save, share, etc.
The purpose of it is to get the file's metadata like name, size, mime type, last modified, etc. and perform actions on
it without the need to copy each file in cache/files directory.
All supported methods are divided in extensions grouped by channels (Action, Activity, Events).
βΉοΈ Methods for directories are marked with
π, for filesπ.
Instantiate DocumentFile
There are two ways to instantiate a DocumentFile:
-
From the uri (content://), saved previously, with persisted permission.
β In rarely cases
DocumentFilecan be instantiated even if the uri doesn't have persisted permission. For example uris likecontent://media/external/file/106cannot be instantiated directly, but if the file was picked through (DocMan.pick.visualMedia()for example), it will be instantiated, but most of the methods will throw an exception, you will be able only to read the file content.Future<DocumentFile?> backupDir() => DocumentFile(uri: 'content://com.android.externalstorage.documents/tree/primary%3ADocMan').get(); -
From the app local
File.pathorDirectory.path.Future<DocumentFile?> file() => DocumentFile(uri: 'path/to/file.jpg').get(); /// If directory doesn't exist, it will create all directories in the path. Future<DocumentFile?> dir() => DocumentFile(uri: 'path/to/some/directory/notCreatedYet').get();
DocumentFile Activity methods
Activity Methods are interactive methods which require user interaction.
Like open, share, saveTo methods. All methods are called through Activity channel.
-
openπOpen the file with supported app.If there are more than one app that can open files of this file type, the system will show a dialog to choose the app to open with. Action can be performed only on file & file must exist.
Future<bool> openFile(DocumentFile file) => file.open('Open with:'); -
shareπShare the file with other apps.Future<bool> shareFile(DocumentFile file) => file.share('Share with:'); -
saveToπSave the file to the selected directory.You can specify the initial directory to start from, whether to show only local directories or not, and delete the original file after saving. After saving, the method returns the saved
DocumentFile.Future<DocumentFile?> saveFile(DocumentFile file) => file.saveTo( initDir: 'content uri to start from', //optional localOnly: true, deleteOriginal: true, );
DocumentFile Events / Stream methods
Methods collection used for stream-based operations like reading files, listing directories, etc.
All methods are called through Events channel.
If DocumentFile is a directory, you can list its files & subdirectories via stream,
if it's a file, you can read it via stream as bytes or string.
-
readAsStringπRead the file content as string stream.Can be used only on file & file must exist. You can specify the encoding of the file content, buffer size or set the start position to read from.
Stream<String> readAsString(DocumentFile file) => file.readAsString(charset: 'UTF-8', bufferSize: 1024, start: 0); -
readAsBytesπRead the file content as bytes stream.Can be used only on file & file must exist. You can specify the buffer size or set the start position to read from.
Stream<Uint8List> readAsBytes(DocumentFile file) => file.readAsBytes(bufferSize: (1024 * 8), start: 0); -
listDocumentsStreamπList the documents in the directory as stream.Can be used only on directory & directory must exist. You can specify the mimeTypes & extensions filter, to filter the documents by type, or filter documents by string in name.
Stream<DocumentFile> listDocumentsStream(DocumentFile dir) => dir.listDocumentsStream(mimeTypes: ['application/pdf'], extensions: ['pdf', '.docx'], nameContains: 'doc_');
DocumentFile Action methods
Action methods are used for file & directory operations. All methods are called through Action channel, and can be performed in the background (with isolates or WorkManager).
-
permissionsππGet the persisted permissions for the file or directory.Returns PersistedPermission instance or
nullif there are no persisted permissions.Future<PersistedPermission?> getPermissions(DocumentFile file) => file.permissions(); -
readπRead the entire file content as bytes.Can be used only on file & file must exist.
Future<Uint8List> readBytes(DocumentFile file) => file.read();βΉοΈ If file is big, it's better to use stream-based method
readAsBytes. -
createDirectoryπCreate a new subdirectory with the specified name.Can be used only on directory & directory must exist & has write permission & flag
canCreateistrue. Returns the createdDocumentFiledirectory.Future<DocumentFile?> createDir(DocumentFile dir) => dir.createDirectory('new_directory'); -
createFileπCreate a new file with the specified name & content in the directory.Can be used only on directory & directory must exist & has write permission & flag
canCreateistrue. You can specify the content of the file as bytes or string, name must contain extension. It will try to determine the mime type from the extension, otherwise it will throw an exception. If the name contains extension only, like in example.txt, name will be generated automatically. Example:.txt->docman_file_18028.txt. Returns the createdDocumentFilefile./// Create a new file with the specified name & String content in the directory. Future<DocumentFile?> createFile(DocumentFile dir) => dir.createFile(name: '.txt', content: 'Hello World!'); /// Create a new file with the specified name & bytes content in the directory. Future<DocumentFile?> createFileFromBytes(DocumentFile dir) => dir.createFile(name: 'test Document.pdf', bytes: Uint8List.fromList([1, 2, 3, 4, 5])); -
listDocumentsπList the documents in the directory.Can be used only on directory & directory must exist. You can specify the mimeTypes & extensions filter, to filter the documents by type, or filter documents by string in name.
Future<List<DocumentFile>> listDocuments(DocumentFile dir) => dir.listDocuments(mimeTypes: ['application/pdf'], extensions: ['pdf', '.docx'], nameContains: 'doc_');βΉοΈ This method returns all documents in the directory, if list has many items, it's better to use stream-based method
listDocumentsStream. -
findπFind the document in the directory by name.Can be used only on directory & directory must exist. Search through
listDocumentsfor the first document exact matching the given name. Returns null when no matching document is found.Future<DocumentFile?> findDocument(DocumentFile dir) => dir.find('file_name.jpg'); -
deleteππDelete the file or directory. Can be used on both file & directory.Works only if the document exists & has permission to write & flag
canDeleteis set totrue. If the document is a directory, it will delete all content recursively. Returnstrueif the document was deleted.Future<bool> deleteFile(DocumentFile file) => file.delete(); Future<bool> deleteDir(DocumentFile dir) => dir.delete(); -
cacheπCopy the file to the cache directory (external if available, internal otherwise).If file with same name already exists in cache, it will be overwritten. Works only if the document exists & has permission to read. Returns
Fileinstance of the cached file./// For all types of files Future<File?> cacheFile(DocumentFile file) => file.cache(); /// If file is image (jpg, png, webp) you can specify the quality of the image Future<File?> cacheImage(DocumentFile file) => file.cache(imageQuality: 70); -
copyToπCopy the file to the specified directory.File must exist & have flag
canReadset totrue. Destination directory must exist & have persisted permissions, or it can be local app directory likeDirectory.path. Optionally You can specify the new name of the file, with or without extension. If something goes wrong, it will throw an exception & created file will be deleted.///Copy file to the the directory `DocumentFile` instance with persisted permission uri Future<DocumentFile?> copyFile(DocumentFile file) => file.copyTo('content://com.android.externalstorage.documents/tree/primary%3ADocMan', name: 'my new file copy'); ///Copy file to the the local app directory `Directory.path` Future<DocumentFile?> copyFileToLocalDir(DocumentFile file) => file.copyTo('/data/user/0/devdf.plugins.docman_example/app_flutter/myDocs', name: 'test_file.txt'); -
moveToπMove the file to the specified directory.File must exist & have flag
canRead&canDeleteset totrue. Destination directory must exist & have persisted permissions, or it can be local app directory likeDirectory.path. Optionally You can specify the new name of the file, with or without extension, otherwise the file will be moved with the same name. If something goes wrong, automatically will delete the created file. Returns theDocumentFileinstance of the moved file. After moving the file, the original file will be deleted.///Move file to the the directory `DocumentFile` instance with persisted permission uri Future<DocumentFile?> moveFile(DocumentFile file) => file.moveTo('content://com.android.externalstorage.documents/tree/primary%3ADocMan', name: 'moved file name'); ///Move file to the the local app directory `Directory.path` Future<DocumentFile?> moveFileToLocalDir(DocumentFile file) => file.moveTo('/data/user/0/devdf.plugins.docman_example/cache/TempDir', name: 'moved_file.txt'); -
thumbnailπGet the thumbnail of the file.Can be used only on file & file must exist & has flag
canThumbnailset totrue. You must specify the width & height of the thumbnail. Optionally you can specify the quality of the image and setpngorwebptotrueto get the compressed image in that format, otherwise it will bejpeg. Returns DocumentThumbnail instance of the thumbnail image ornullif the thumbnail is not available. Commonly used for images, videos, pdfs.Future<DocumentThumbnail?> thumbnail(DocumentFile file) => file.thumbnail(width: 256, height: 256, quality: 70);β οΈ Sometimes due to different document providers, thumbnail can have bigger dimensions, than requested. Some document providers may not support thumbnail generation.
β οΈ If file is local image, only
jpg,png,webp,giftypes are currently supported for thumbnail generation, in all other cases support depends on the document provider. -
thumbnailFileπGet the thumbnail of the file as aFile.Same as
thumbnailmethod, but returns the thumbnail image as aFileinstance, saved in the cache directory. First it will try to save to external cache directory, if not available, then to internal cache directory.Future<File?> thumbnailFile(DocumentFile file) => file.thumbnailFile(width: 192, height: 192, webp: true);
π§© DocumentThumbnail class
DocumentThumbnail is a data class that holds information about the thumbnail image.
It stores the width, height of the image, and the bytes (Uint8List) of the image.
Unsupported methods
Information about currently (temporarily) unsupported methods in the plugin.
β οΈ Currently
πrenameaction was commented out due to the issue with the SAF API. Very few Documents Providers support renaming files & after renaming, the document may not be found, so it's better to usecopy&deleteactions instead.
πΌοΈ DocumentFile examples (click for expand/collapse)
| Local file activity | Picked File actions |
|---|---|
| Picked Directory actions | Local Directory actions |
|---|---|
ποΈ DocMan Exceptions #
DocMan provides a set of exceptions that can be thrown during the plugin operation.
DocManException- Base exception for all exceptions.
Common exceptions for all channels:
AlreadyRunningExceptionThrown when the same method is already in progress.NoActivityExceptionThrown when the activity is not available. For example when you try to perform activity actions likeopen,share,saveToorpick& no activity found to handle the request.
π DocManAppDirs() (DocMan.dir) exceptions:
AppDirPathExceptionThrown when the app directory path is not found. For example if device doesn't have external storage.AppDirActionExceptionThrown when app tries to perform unimplemented action on app directory.
π DocManPicker() (DocMan.pick) exceptions:
PickerMimeTypeExceptionThrown forDocMan.pick.visualMedia()method, when mimeTypes are not supported.PickerMaxLimitExceptionThrown forDocMan.pick.visualMedia()method. Whenlimitparameter is greater than max allowed by the platform, currently it uses MediaStore.getPickImagesMaxLimit on supported devices (Android 11 & above), otherwise it forces the limit to 100.PickerCountExceptionThrown when you set picker parameterlimitResultCanceltotrue. This exception has 2 String properties:count- number of picked files,limit- the limit set. For example when you pick 5 files, butlimitis set to 3 andlimitResultCancelistrue.
π DocumentFile exceptions:
DocumentFileExceptionBase exception for allDocumentFileexceptions thrown by the plugin.
π‘οΈ DocManPermissionManager() (DocMan.perms) exceptions:
PermissionsExceptionBase exception thrown by the permissions' manager for all methods.
π¦ Changelog #
Please see CHANGELOG.md for more information on what has changed recently.
βοΈ Help & Questions #
Start a new discussion in the Discussions Tab.
π± Contributing #
Any contributions you make are greatly appreciated.
Just fork the repository and create a pull request.
For major changes, please first start a discussion in the Discussions Tab to discuss what you would like to change.
βΌοΈ By submitting a patch, you agree to allow the project owner(s) to license your work under the terms of the
MIT License.
π Thank you!