plux_media_picker 1.0.2
plux_media_picker: ^1.0.2 copied to clipboard
A Flutter plugin that provides a unified API to pick photos, videos, and files from the device's gallery, camera, and file system.
plux_media_picker #
A Flutter plugin that provides a unified API to pick photos, videos, and files from the device's gallery, camera, and file system. Supports iOS and Android only.
Features #
- Uses native system pickers (no custom UI)
- Single or multiple selection of media files
- Built-in image compression with adjustable quality
- File extension filtering for the file picker
- Streaming reads of any picked file, chunk by chunk, without loading it into memory
- Handles Android activity recreation (returns lost results via
getLostFiles) - Copies a file only when it has to: compression is the single reason the plugin ever writes to the cache, everything else is read from where the user keeps it
Requirements #
| Platform | Minimum version |
|---|---|
| Android | minSdk 24, host activity must be a ComponentActivity |
| iOS | 13.0 (CocoaPods) / 15.0 (Swift Package Manager) |
Installation #
Add this to your pubspec.yaml:
dependencies:
plux_media_picker: ^1.0.2
Setup #
Android #
The camera and gallery pickers use registerForActivityResult, which requires the host activity
to extend ComponentActivity. The default FlutterActivity does not, so change
MainActivity to FlutterFragmentActivity:
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity()
Without this the plugin throws IllegalStateException when the activity attaches.
Only the camera needs a permission:
<uses-permission android:name="android.permission.CAMERA" />
The plugin requests it at runtime and reports a denial as
PluxMediaPickerExceptionCode.permissionAccessDenied. Picking from the gallery goes through the
Android photo picker, which runs in a separate process and grants access to the chosen items
only, so no storage permission is required. The FileProvider used for camera captures is
declared by the plugin's own manifest.
iOS #
Add the following keys to ios/Runner/Info.plist:
<key>NSCameraUsageDescription</key>
<string>We need camera access to take photos</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need microphone access to record video</string>
NSMicrophoneUsageDescription is required only if you use CameraMediaType.video.
NSPhotoLibraryUsageDescription is not needed: the gallery is shown by PHPickerViewController,
which runs out of process and returns only what the user picked.
Usage #
First, create an instance of the plugin:
import 'package:plux_media_picker/plux_media_picker.dart';
final picker = PluxMediaPicker();
Pick from Camera #
// Take a photo
PluxFile? photo = await picker.pickCamera(mediaType: CameraMediaType.image, quality: 0.8);
// Record a video
PluxFile? video = await picker.pickCamera(mediaType: CameraMediaType.video, quality: 0.8);
quality is the JPEG quality in the 0.0 – 1.0 range and applies to images only — a
recording is handed over exactly as the camera produced it.
Pick from Gallery #
List<PluxFile> result = await picker.pickGallery(maxLimit: 10, quality: 0.8);
An image is copied only when it has to be re-encoded, that is when quality < 1.0. Videos and
full quality images keep their original location.
Pick from File System #
// Pass extensions without a leading dot; an empty list allows any file type.
PluxFile? result = await picker.pickFile(allowedExtensions: ['pdf', 'txt']);
The document is never copied, so PluxFile.path is empty and PluxFile.uri is the only handle —
read it with readFileStream.
Extensions are mapped to MIME types on Android and to UTTypes on iOS; ones that cannot be
resolved are logged and skipped, and if none of them resolve the picker shows all files.
Reading a file as a stream #
readFileStream opens a native reader for a PluxFile.uri and emits the content in chunks,
which keeps large files off the Dart heap:
final stream = await picker.readFileStream(result.uri);
var received = 0;
await for (final Uint8List chunk in stream) {
received += chunk.length;
debugPrint('$received / ${result.size}');
}
Every PluxFile this plugin returns can be read this way, including recovered lost files.
Reading always starts at the beginning of the file.
Cancelling the subscription stops the native reader as well:
final subscription = stream.listen(handleChunk);
await subscription.cancel();
bufferSize sets the chunk size (256 KB by default). Larger chunks mean fewer platform messages
and more memory per chunk:
final stream = await picker.readFileStream(result.uri, bufferSize: 4 * 1024 * 1024);
Each call creates its own stream, so several files can be read at the same time, and the channel
is torn down as soon as the stream ends or is cancelled. If the native side cannot open the uri,
the call throws PluxMediaPickerExceptionCode.fileStreamCreationFailed.
uri and path #
PluxFile.uri is always set and is what readFileStream needs. PluxFile.path is set only when
the plugin actually created a file, so it is empty exactly where nothing was copied:
| Source | uri |
path |
|---|---|---|
pickCamera |
always | always (the capture is the plugin's own file) |
pickGallery |
always | only for re-encoded images on Android; always on iOS |
pickFile |
always | never |
When path is empty, dart:io cannot open the file — on Android the uri belongs to another app,
and on iOS it is security scoped. Use readFileStream instead.
How long a pickFile uri stays readable differs by platform: Android takes a persistable grant,
so it survives a restart until clearCache releases it, while on iOS the scoped url is valid for
the current run of the app only. Do not store it as a long lived reference on iOS.
Recovering Lost Results (Android only) #
If your app is killed by the system while the picker is open, the plugin saves the selected result. To retrieve it after the activity is recreated, call getLostFiles() (e.g., in initState):
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
List<PluxFile> lostFiles = await picker.getLostFiles();
if (lostFiles.isNotEmpty) {
// handle the recovered files - they carry a uri and can be streamed
}
});
}
A recovered result is reported once and then forgotten. On iOS this always returns an empty list.
Clearing the cache #
The plugin writes compressed images and camera captures into its own directory inside the app cache. Once you have copied what you need, drop them:
await picker.clearCache();
Only the plugin's own files are removed; the rest of the app cache is left alone. Any
PluxFile.path from an earlier pick becomes invalid afterwards. On Android the call also
releases the access granted to documents picked with pickFile, so uris returned earlier stop
working.
Return Values & Error Handling #
pickCamerareturnsnullif the user cancels.pickGalleryreturns an empty list[]if nothing is selected.pickFilereturnsnullif nothing is selected.- All three throw
PluxMediaPickerExceptionon failure (denied permission, compression or save error); wrap calls intry-catch. - Calling a picker while another call of the same kind is pending fails the new call instead of abandoning the previous one.
readFileStreamthrowsPluxMediaPickerExceptionif the stream cannot be created, and forwards read errors to the stream'sonError.
try {
final file = await picker.pickCamera();
} on PluxMediaPickerException catch (ex) {
debugPrint('${ex.code}: ${ex.description}');
}
PluxMediaPickerExceptionCode values: loadFileFailed, invalidImageData, compressionFailed,
saveFailed, invalidFile, permissionAccessDenied, mediaTypeGetFailed,
fileStreamCreationFailed.
API Reference #
| Method | Parameters | Returns | Description |
|---|---|---|---|
pickCamera |
mediaType: CameraMediaType (default image), quality: double (0.8) |
Future<PluxFile?> |
Opens the camera for a single media file. |
pickGallery |
maxLimit: int (default 10), quality: double (default 0.8) |
Future<List<PluxFile>> |
Opens the gallery for media selection. |
pickFile |
allowedExtensions: List<String> (default []) |
Future<PluxFile?> |
Opens the file manager. |
readFileStream |
uri: String, bufferSize: int (default 256 KB) |
Future<Stream<Uint8List>> |
Reads the file behind a PluxFile.uri in chunks. |
getLostFiles |
– | Future<List<PluxFile>> |
Retrieves files selected before activity recreation (Android). |
clearCache |
– | Future<bool> |
Clears the files the plugin created. |
PluxFile #
| Field | Description |
|---|---|
uri |
Platform uri, always set; the handle readFileStream expects |
path |
Absolute file path, set only when the plugin created a file |
name |
File name with extension |
size |
Size in bytes |
Example #
The app in example/ exercises every method: camera photo and video with a quality
slider, gallery multi-select with a limit, file picking with an extension filter, streamed reads
with a chunk size selector, progress and cancellation, lost-file recovery and cache clearing.