system_media_picker 1.0.1 copy "system_media_picker: ^1.0.1" to clipboard
system_media_picker: ^1.0.1 copied to clipboard

Permission-free image and video picker for Flutter. Wraps the Android System Photo Picker and iOS PHPickerViewController behind a single Dart API — no READ_MEDIA_* / NSPhotoLibrary* declarations needed.

system_media_picker #

pub package Permission free License: MIT

A permission-free Flutter plugin for picking multiple images and videos from the user's gallery on Android and iOS. Bridges the Android System Photo Picker and iOS PHPickerViewController behind a single Dart API.

Highlights #

  • Zero photo / media / storage permissions on either platform.
  • Multiple images or videos per pick (1–20, dynamic).
  • Hard cap enforced end-to-end by the system picker UI itself.
  • Returns ready-to-use dart:io File paths in the app's private cache.
  • Optional MediaPickerController (ChangeNotifier) with idle / loading / success / error state.
  • No third-party dependencies.

Platform support #

Platform Mechanism Min OS Permission
Android MediaStore.ACTION_PICK_IMAGES (or SAF) API 21 (minSdk 21) None
iOS PHPickerViewController iOS 14+ (gated at runtime) None

On Android 11/12 (API 30–32) the Photo Picker is delivered through the modular system component shipped via Google Play system updates. On Android 10 and below the plugin transparently falls back to the Storage Access Framework (ACTION_OPEN_DOCUMENT) — also permission-free.

On iOS the plugin's CocoaPod system_media_picker.podspec declares an iOS 13.0 deployment target so host apps don't have to bump theirs; PHPickerViewController is gated to iOS 14+ at runtime via if #available(iOS 14, *).

Installation #

flutter pub add system_media_picker

…or, manually:

dependencies:
  system_media_picker: ^1.0.0

That's it. No changes are required to your AndroidManifest.xml, your iOS Info.plist, or any platform-side initialisation code. The plugin is auto-registered through Flutter's plugin discovery.

Quick start #

1) Function-style API — SystemMediaPicker #

import 'package:system_media_picker/system_media_picker.dart';

final picker = SystemMediaPicker();

// Image picker (1..20)
final images = await picker.pickImages(limit: 5);
for (final image in images) {
  debugPrint('${image.name} → ${image.path} (${image.sizeBytes} bytes)');
}

// Video picker (1..20)
final videos = await picker.pickVideos(limit: 3);

// Probe the device
final hasModernPicker = await picker.isAvailable();

PickedMedia exposes:

Field Type Notes
path String Absolute path inside the app's cache.
uri String Original content:// (Android) / file URL.
name String Best-effort display name.
sizeBytes int File size in bytes.
mimeType String image/jpeg, video/mp4, …
type MediaType image, video, or unknown.
file File Convenience accessor for dart:io.
isImage bool Sugar for type == MediaType.image.
isVideo bool Sugar for type == MediaType.video.

2) UI-friendly API — MediaPickerController #

A ChangeNotifier that owns the current selection, enforces the limit, and tracks loading / success / error state for you.

class MyPage extends StatefulWidget {
  const MyPage({super.key});
  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  final controller = MediaPickerController(initialLimit: 8);

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, _) {
        return Column(
          children: [
            switch (controller.status) {
              PickerStatus.loading =>
                  const LinearProgressIndicator(),
              PickerStatus.error =>
                  Text(controller.errorMessage ?? 'Unknown error'),
              _ => const SizedBox.shrink(),
            },
            Expanded(child: MyGrid(items: controller.items)),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: controller.isLoading || controller.isAtCapacity
                      ? null
                      : controller.pickImages,
                  child: const Text('Pick images'),
                ),
                ElevatedButton(
                  onPressed: controller.isLoading || controller.isAtCapacity
                      ? null
                      : controller.pickVideos,
                  child: const Text('Pick videos'),
                ),
              ],
            ),
          ],
        );
      },
    );
  }
}

API surface:

  • items, imageCount, videoCount, limit
  • status, isLoading, isAtCapacity, remainingSlots, errorMessage
  • pickImages(), pickVideos(), pick(MediaType)
  • updateLimit(int), removeAt(int), clear()

Errors #

All failures are subclasses of the sealed MediaPickerException:

Exception When it's thrown
InvalidLimitException limit is outside [1, 20].
PickerBusyException A pick request is already in flight.
PickerLaunchException Native side could not launch the picker.
PickerUnsupportedOsException iOS < 14 (no PHPickerViewController).
UnsupportedPlatformException Host platform is neither Android nor iOS.

Permissions {#permissions} #

This plugin is the whole reason you can ditch the legacy permissions:

Android #

Mechanism Permission required
MediaStore.ACTION_PICK_IMAGES (System Photo Picker) None
Intent.ACTION_OPEN_DOCUMENT (Storage Access Framework) None

Both run in a separate system process and grant per-item URI access to the calling app. Do not add READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_EXTERNAL_STORAGE, or WRITE_EXTERNAL_STORAGE to your manifest.

iOS #

Mechanism Permission required
PHPickerViewController None (Apple docs)

"Because the user must explicitly grant access to each photo or video they want to share through the picker, you don't need to request authorization to use this picker."

Do not add NSPhotoLibraryUsageDescription or NSPhotoLibraryAddUsageDescription to Info.plist.

Architecture #

system_media_picker/
├── lib/
│   ├── system_media_picker.dart          ← public exports
│   └── src/
│       ├── system_media_picker.dart      ← MethodChannel facade
│       ├── exceptions.dart
│       ├── models/
│       │   ├── media_type.dart
│       │   └── picked_media.dart
│       └── controller/
│           └── media_picker_controller.dart
├── android/
│   └── src/main/kotlin/com/example/system_media_picker/
│       ├── SystemMediaPickerPlugin.kt    ← FlutterPlugin + ActivityAware
│       └── SystemPhotoPickerHandler.kt   ← Photo Picker / SAF logic
└── ios/
    └── Classes/
        └── SystemMediaPickerPlugin.swift ← PHPickerViewController bridge

Limitations #

  • Cache-backed paths. The native side copies each chosen file into the app's cache directory. Move them somewhere durable (getApplicationDocumentsDirectory()) if you need long-term persistence.
  • System cap (Android). MediaStore.getPickImagesMaxLimit() may be lower than 20 on some devices — the more restrictive cap wins.
  • iOS 13. The picker throws PickerUnsupportedOsException. There is no UIImagePickerController fallback on purpose: that path requires NSPhotoLibraryUsageDescription.
  • Video previews. The plugin does not ship video-thumbnail decoding. Drop in video_thumbnail or video_player if you need real frame thumbnails.

License #

MIT — see LICENSE.

0
likes
140
points
19
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Permission-free image and video picker for Flutter. Wraps the Android System Photo Picker and iOS PHPickerViewController behind a single Dart API — no READ_MEDIA_* / NSPhotoLibrary* declarations needed.

Repository (GitHub)
View/report issues

Topics

#picker #media #images #video #photos

License

MIT (license)

Dependencies

flutter

More

Packages that depend on system_media_picker

Packages that implement system_media_picker