file_picker_next

A Flutter plugin for picking files that fully complies with Google Play's Photo and Video Permissions policy.

pub package License: MIT

🎯 Why file_picker_next?

Google Play now rejects apps that request READ_MEDIA_IMAGES or READ_MEDIA_VIDEO permissions for one-time file access. This plugin solves that problem by using:

  • Android: Storage Access Framework (SAF) + Android Photo Picker - NO permissions required
  • iOS: UIDocumentPickerViewController + Native Photo Picker - NO extra permissions

✨ Features

  • Zero storage permissions on Android - fully Play Store compliant
  • 📁 Pick single or multiple files
  • 🖼️ Support for images, videos, documents, and custom file types
  • 💾 Save files to Downloads (Android) / Documents (iOS)
  • 📷 Camera integration via image_picker
  • 🔍 Filter by file extensions
  • 📊 Get file metadata (name, size, path, bytes)
  • 🚀 Simple, straightforward API
  • 📱 Supports Android 5.0+ and iOS 15.0+

📦 Installation

Add this to your pubspec.yaml:

dependencies:
  file_picker_next: ^0.1.2

Then run:

flutter pub get

🚀 Quick Start

Pick Images

import 'package:file_picker_next/file_picker_next.dart';

// Pick a single image
final images = await FilePickerNext.pickFiles(
  type: FilePickerType.image,
);

if (images != null) {
  for (var image in images) {
    print('Picked: ${image.name}, Size: ${image.size} bytes');
  }
}

Pick Multiple Documents

// Pick multiple PDF files
final files = await FilePickerNext.pickFiles(
  allowMultiple: true,
  type: FilePickerType.custom,
  allowedExtensions: ['pdf', 'doc', 'docx'],
);

if (files != null) {
  for (var file in files) {
    print('${file.name}: ${file.path}');
  }
}

Pick with Camera Option

// Show dialog to choose camera or gallery
final file = await FilePickerNext.showMediaSourceDialog(
  context,
  isVideo: false, // false for photo, true for video
);

if (file != null) {
  print('Captured/Selected: ${file.name}');
}

Save File to Downloads

final bytes = await file.readAsBytes();
final path = await FilePickerNext.saveToDownloads(
  fileName: 'my_file.pdf',
  bytes: bytes,
  mimeType: 'application/pdf',
);

print('Saved to: $path');

📖 API Reference

FilePickerNext

Main class for file picking operations.

Methods

pickFiles()

Pick one or more files from device storage.

static Future<List<PickedFileInfo>?> pickFiles({
  bool allowMultiple = false,
  FilePickerType type = FilePickerType.any,
  List<String>? allowedExtensions,
})

Parameters:

  • allowMultiple: Allow selecting multiple files (default: false)
  • type: Type of files to pick - see FilePickerType enum
  • allowedExtensions: List of allowed extensions (e.g., ['pdf', 'doc']) when type is FilePickerType.custom

Returns: List of PickedFileInfo or null if cancelled

showMediaSourceDialog()

Show a bottom sheet dialog to choose between camera and gallery.

static Future<PickedFileInfo?> showMediaSourceDialog(
  BuildContext context, {
  bool isVideo = false,
})

Parameters:

  • context: Flutter BuildContext
  • isVideo: true for video, false for photo (default: false)

Returns: PickedFileInfo or null if cancelled

saveToDownloads()

Save a file to the Downloads folder (Android) or Documents folder (iOS).

static Future<String?> saveToDownloads({
  required String fileName,
  required List<int> bytes,
  String mimeType = 'application/octet-stream',
})

Parameters:

  • fileName: Name of the file to save
  • bytes: File content as bytes
  • mimeType: MIME type of the file

Returns: Saved file path or null on error

FilePickerType

Enum defining the type of files to pick.

enum FilePickerType {
  any,      // Any file type
  image,    // Images only
  video,    // Videos only
  media,    // Images and videos
  custom,   // Custom extensions (use allowedExtensions)
}

PickedFileInfo

Model class representing a picked file.

Properties

  • String path - Absolute path to the file
  • String name - File name with extension
  • int size - File size in bytes
  • List<int>? bytes - File content as bytes (optional)

Getters

  • String extension - File extension (lowercase)
  • bool isImage - Whether file is an image
  • bool isVideo - Whether file is a video
  • bool isDocument - Whether file is a document
  • File file - Dart File object
  • String mimeType - MIME type based on extension

🔧 Platform Setup

Android

No additional setup required!

The plugin uses Storage Access Framework, which doesn't require any permissions. Your AndroidManifest.xml stays clean.

Optional: Camera Access

If you want to use the camera feature via showMediaSourceDialog(), add these permissions:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" android:required="false" />
</manifest>

iOS

No additional setup for document picking!

Optional: Camera Access

If you want to use the camera feature, add these to your Info.plist:

<key>NSCameraUsageDescription</key>
<string>We need camera access to take photos</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need photo library access to select images</string>

🎨 Complete Example

import 'package:flutter/material.dart';
import 'package:file_picker_next/file_picker_next.dart';

class FilePickerDemo extends StatefulWidget {
  @override
  _FilePickerDemoState createState() => _FilePickerDemoState();
}

class _FilePickerDemoState extends State<FilePickerDemo> {
  List<PickedFileInfo> _files = [];

  Future<void> _pickImages() async {
    final files = await FilePickerNext.pickFiles(
      type: FilePickerType.image,
      allowMultiple: true,
    );
    
    if (files != null) {
      setState(() => _files = files);
    }
  }

  Future<void> _pickDocuments() async {
    final files = await FilePickerNext.pickFiles(
      type: FilePickerType.custom,
      allowedExtensions: ['pdf', 'doc', 'docx'],
      allowMultiple: true,
    );
    
    if (files != null) {
      setState(() => _files = files);
    }
  }

  Future<void> _capturePhoto() async {
    final file = await FilePickerNext.showMediaSourceDialog(context);
    
    if (file != null) {
      setState(() => _files = [file]);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('File Picker Next')),
      body: Column(
        children: [
          ElevatedButton(
            onPressed: _pickImages,
            child: Text('Pick Images'),
          ),
          ElevatedButton(
            onPressed: _pickDocuments,
            child: Text('Pick Documents'),
          ),
          ElevatedButton(
            onPressed: _capturePhoto,
            child: Text('Take Photo'),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _files.length,
              itemBuilder: (context, index) {
                final file = _files[index];
                return ListTile(
                  title: Text(file.name),
                  subtitle: Text('${file.size} bytes'),
                  leading: file.isImage
                      ? Image.file(file.file, width: 50, height: 50, fit: BoxFit.cover)
                      : Icon(Icons.insert_drive_file),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

🆚 Migration from file_picker

Migrating from the popular file_picker package is straightforward:

file_picker file_picker_next
FilePicker.platform.pickFiles() FilePickerNext.pickFiles()
FileType.image FilePickerType.image
FileType.custom FilePickerType.custom
PlatformFile PickedFileInfo
result.files Direct list return

❓ FAQ

Why do I need this plugin?

If you've received this error from Google Play:

"Your app is not compliant with how the READ_MEDIA_IMAGES/READ_MEDIA_VIDEO permissions are allowed to be used."

This plugin solves that by using system pickers instead of requesting permissions.

Does it work with image_picker?

Yes! For photo/video picking, we internally use image_picker which is fully compliant.

Can I get file bytes?

Yes! PickedFileInfo includes a bytes property with the file content, or you can use file.readAsBytes().

What about web support?

Currently supports Android and iOS. Web support may be added in future versions.

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Credits

This plugin was created to help Flutter developers comply with Google Play's new Photo and Video permissions policy.


Made with ❤️ for the Flutter community

Libraries

file_picker_next
A Flutter plugin for picking files that complies with Google Play's Photo and Video Permissions policy.