Magic Logo

Magic Picker

Pick a file, an image or a video and get a MagicFile back.
The Pick facade for Magic apps opens the gallery, camera, file dialog and save dialog on every platform, and PickerServiceProvider lets Storage.download open the share sheet on mobile, macOS and Windows and the save dialog on Linux.

pub package CI codecov License: MIT pub points

Documentation · pub.dev · Issues

At a glance

Fact Value
Package magic_picker
Install flutter pub add magic_picker, then dart run magic:dowel plugin:install magic_picker
Import package:magic_picker/magic_picker.dart
Entry points Pick (facade), PickerServiceProvider (wiring, download handler), XFile.toMagicFile() and MagicFile.toXFile() (extensions); re-exports XFile and CameraDevice
Platforms Android, iOS, web, macOS, Windows and Linux. The camera on desktop needs a custom image_picker delegate; Pick.directory is not available on the web.
Requires Dart >=3.13, Flutter >=3.47, magic ^1.0.0
Dependencies magic, dowel (runs the installer), file_picker, image_picker, share_plus
License MIT

Install

flutter pub add magic_picker
dart run magic:dowel plugin:install magic_picker

The installer registers PickerServiceProvider in the providers list of lib/config/app.dart. It publishes no config: Pick needs no settings.

Pick

Open a picker and store the result.

import 'package:magic_picker/magic_picker.dart';

Future<void> main() async {
  final image = await Pick.image(maxWidth: 1024, imageQuality: 85);
  if (image == null) return; // The user cancelled.

  await image.store('avatars/profile.jpg');
}
Member Returns Meaning
Pick.image, Pick.media, Pick.video Future<MagicFile?> One image, one image or video, one video from the gallery.
Pick.images Future<List<MagicFile>> Several images; empty on cancel.
Pick.camera, Pick.recordVideo Future<MagicFile?> A photo or a recording; fallbackToGallery and onError handle a refused permission.
Pick.file, Pick.files Future<MagicFile?>, Future<List<MagicFile>> One or several files, optionally filtered by extensions.
Pick.directory Future<String?> A directory path; not on the web.
Pick.saveFile Future<Uri?> Writes bytes through the save dialog; the Uri is a file uri on desktop and iOS, and on Android only the path part of the document uri, with no scheme; on the web it answers null after a browser download.

A cancelled pick answers null (a list: empty), never an error. File contents are read on demand, not at pick time.

Download

Storage.download needs a handler on mobile and desktop, and PickerServiceProvider installs it.

import 'package:magic/magic.dart';

Future<void> main() async {
  await Storage.download('reports/2024.pdf', name: 'Annual Report.pdf');
}
Platform What happens
Android, iOS, macOS and Windows PickerServiceProvider.boot() sets LocalAdapterContract.downloadHandler; the file opens in the share sheet.
Linux The same handler opens the save dialog with the file's bytes, because share_plus cannot share a file on Linux.
Web A browser download. The handler is ignored.
Mobile or desktop without the plugin download throws UnsupportedError, naming magic_picker.

XFile

Convert a picked XFile to a MagicFile and back.

import 'package:magic/magic.dart';
import 'package:magic_picker/magic_picker.dart';

Future<void> roundTrip(XFile picked) async {
  final MagicFile file = await picked.toMagicFile();
  print('${file.name}: ${file.size} bytes');

  final XFile xFile = await Future.value(file.toXFile());
  print(xFile.name);
}

toMagicFile() is a Future everywhere. toXFile() is synchronous on mobile and desktop, where it wraps the path, and a Future on the web, where it reads the bytes first. It throws UnsupportedError for a file with no path (IO) or no bytes (web).

For AI coding agents

Rules for an agent writing code against this package. Each one is a mistake that costs a user a crash or a silent no-op.

Do Because
Import only package:magic_picker/magic_picker.dart Everything under src/ is private; the barrel also exports XFile and CameraDevice.
Register PickerServiceProvider in lib/config/app.dart It is not auto-registered, and without it Storage.download throws on mobile and desktop.
Treat a null or empty result as a cancelled pick Cancelling is not an error. Pick.saveFile on the web is the exception: null there is the normal answer once the browser was asked to download, and it cannot tell a finished save from a cancelled save prompt.
Pass onError or fallbackToGallery to Pick.camera and Pick.recordVideo Otherwise a refused camera permission rethrows.
Read savedTo.scheme before toFilePath() on a Pick.saveFile result Only file uris are paths you can open; Android answers the document uri's path part with no scheme, and the web answers null after a browser download.
await toXFile() in code that also builds for the web It is a Future there and synchronous elsewhere.
Do not pass withData to Pick.file or Pick.files The parameter no longer exists; call readAsBytes on the result.
In a test, reset LocalAdapterContract.downloadHandler to null in tearDown It is a static that Magic.flush() does not clear.

Dependencies

magic for the container, MagicFile, Storage and the service provider base class; image_picker for the gallery and camera; file_picker for files, directories and the save dialog; share_plus for the share sheet; dowel to run the installer through dart run magic:dowel.

License

MIT, see LICENSE. Built by Anılcan Çakır.

Libraries

cli
The CLI surface of magic_picker, kept apart from the runtime barrel.
magic_picker
File and image picking for Magic apps.