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.dev. 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
DocumentFile
can be instantiated even if the uri doesn't have persisted permission. For example uris likecontent://media/external/file/106
cannot 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.path
orDirectory.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
null
if 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
canCreate
istrue
. Returns the createdDocumentFile
directory.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
canCreate
istrue
. 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 createdDocumentFile
file./// 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
listDocuments
for 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
canDelete
is set totrue
. If the document is a directory, it will delete all content recursively. Returnstrue
if 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
File
instance 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
canRead
set 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
&canDelete
set 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 theDocumentFile
instance 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
canThumbnail
set totrue
. You must specify the width & height of the thumbnail. Optionally you can specify the quality of the image and setpng
orwebp
totrue
to get the compressed image in that format, otherwise it will bejpeg
. Returns DocumentThumbnail instance of the thumbnail image ornull
if 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
,gif
types 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
thumbnail
method, but returns the thumbnail image as aFile
instance, 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
đ
rename
action 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
&delete
actions 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:
AlreadyRunningException
Thrown when the same method is already in progress.NoActivityException
Thrown when the activity is not available. For example when you try to perform activity actions likeopen
,share
,saveTo
orpick
& no activity found to handle the request.
đ DocManAppDirs()
(DocMan.dir
) exceptions:
AppDirPathException
Thrown when the app directory path is not found. For example if device doesn't have external storage.AppDirActionException
Thrown when app tries to perform unimplemented action on app directory.
đ DocManPicker()
(DocMan.pick
) exceptions:
PickerMimeTypeException
Thrown forDocMan.pick.visualMedia()
method, when mimeTypes are not supported.PickerMaxLimitException
Thrown forDocMan.pick.visualMedia()
method. Whenlimit
parameter 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.PickerCountException
Thrown when you set picker parameterlimitResultCancel
totrue
. This exception has 2 String properties:count
- number of picked files,limit
- the limit set. For example when you pick 5 files, butlimit
is set to 3 andlimitResultCancel
istrue
.
đ DocumentFile
exceptions:
DocumentFileException
Base exception for allDocumentFile
exceptions thrown by the plugin.
đĄī¸ DocManPermissionManager()
(DocMan.perms
) exceptions:
PermissionsException
Base 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!