universal_file_picker 0.1.0 copy "universal_file_picker: ^0.1.0" to clipboard
universal_file_picker: ^0.1.0 copied to clipboard

A cross-platform Flutter package for picking, previewing, and uploading files with a single, consistent API across Android, iOS, Web, macOS, Windows, and Linux.

example/lib/main.dart

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Universal File Picker Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<UFile> _pickedFiles = [];
  final List<UploadResult> _uploadResults = [];
  bool _isUploading = false;
  double _uploadProgress = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Universal File Picker Demo'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildPickSection(),
            const SizedBox(height: 24),
            if (_pickedFiles.isNotEmpty) ...[
              _buildPreviewSection(),
              const SizedBox(height: 24),
              _buildUploadSection(),
              const SizedBox(height: 24),
              _buildInspectorSection(),
            ],
          ],
        ),
      ),
    );
  }

  Widget _buildPickSection() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Pick Files',
              style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                ElevatedButton.icon(
                  onPressed: _pickSingleFile,
                  icon: const Icon(Icons.file_open),
                  label: const Text('Pick Single File'),
                ),
                ElevatedButton.icon(
                  onPressed: _pickMultipleFiles,
                  icon: const Icon(Icons.folder_open),
                  label: const Text('Pick Multiple Files'),
                ),
                ElevatedButton.icon(
                  onPressed: _pickImages,
                  icon: const Icon(Icons.image),
                  label: const Text('Pick Images'),
                ),
                ElevatedButton.icon(
                  onPressed: _pickDocuments,
                  icon: const Icon(Icons.description),
                  label: const Text('Pick Documents'),
                ),
                ElevatedButton.icon(
                  onPressed: _pickWithCamera,
                  icon: const Icon(Icons.camera_alt),
                  label: const Text('Camera'),
                ),
              ],
            ),
            if (_pickedFiles.isNotEmpty) ...[
              const SizedBox(height: 16),
              Text(
                'Picked Files (${_pickedFiles.length}):',
                style: Theme.of(context).textTheme.titleMedium?.copyWith(
                      fontWeight: FontWeight.bold,
                    ),
              ),
              const SizedBox(height: 8),
              SizedBox(
                height: 80,
                child: ListView.builder(
                  scrollDirection: Axis.horizontal,
                  itemCount: _pickedFiles.length,
                  itemBuilder: (context, index) {
                    final file = _pickedFiles[index];
                    return Container(
                      width: 120,
                      margin: const EdgeInsets.only(right: 8),
                      padding: const EdgeInsets.all(8),
                      decoration: BoxDecoration(
                        border: Border.all(color: Colors.grey[300]!),
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Icon(
                            _getFileIcon(file.mimeType),
                            size: 24,
                            color: Colors.blue[600],
                          ),
                          const SizedBox(height: 4),
                          Text(
                            file.name,
                            style: const TextStyle(fontSize: 10),
                            textAlign: TextAlign.center,
                            maxLines: 2,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ],
                      ),
                    );
                  },
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

  Widget _buildPreviewSection() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'File Preview',
              style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
            ),
            const SizedBox(height: 16),
            if (_pickedFiles.isNotEmpty)
              UFilePreview(
                file: _pickedFiles.first,
                height: 300,
                showFileInfo: true,
              ),
          ],
        ),
      ),
    );
  }

  Widget _buildUploadSection() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Upload Files',
              style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
            ),
            const SizedBox(height: 16),
            if (_isUploading) ...[
              LinearProgressIndicator(value: _uploadProgress),
              const SizedBox(height: 8),
              Text(
                  'Uploading... ${(_uploadProgress * 100).toStringAsFixed(1)}%'),
              const SizedBox(height: 16),
            ],
            Row(
              children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _isUploading ? null : _uploadFiles,
                    icon: const Icon(Icons.upload),
                    label: const Text('Upload All Files'),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton.icon(
                  onPressed: _isUploading ? _cancelUpload : null,
                  icon: const Icon(Icons.cancel),
                  label: const Text('Cancel'),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.red[50],
                    foregroundColor: Colors.red[700],
                  ),
                ),
              ],
            ),
            if (_uploadResults.isNotEmpty) ...[
              const SizedBox(height: 16),
              Text(
                'Upload Results:',
                style: Theme.of(context).textTheme.titleMedium?.copyWith(
                      fontWeight: FontWeight.bold,
                    ),
              ),
              const SizedBox(height: 8),
              ..._uploadResults.map((result) => Card(
                    margin: const EdgeInsets.only(bottom: 8),
                    child: ListTile(
                      leading: Icon(
                        result.success ? Icons.check_circle : Icons.error,
                        color: result.success ? Colors.green : Colors.red,
                      ),
                      title: Text(
                        result.success ? 'Success' : 'Failed',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: result.success
                              ? Colors.green[700]
                              : Colors.red[700],
                        ),
                      ),
                      subtitle: Text(
                        result.error ??
                            'Uploaded ${result.bytesUploaded} bytes',
                      ),
                    ),
                  )),
            ],
          ],
        ),
      ),
    );
  }

  Widget _buildInspectorSection() {
    return UFileInspector(
      files: _pickedFiles,
      uploadResults: _uploadResults,
    );
  }

  Future<void> _pickSingleFile() async {
    try {
      final file = await UniversalFilePicker().pickFile();
      if (file != null) {
        setState(() {
          _pickedFiles.clear();
          _pickedFiles.add(file);
        });
      }
    } catch (e) {
      _showError('Failed to pick file: $e');
    }
  }

  Future<void> _pickMultipleFiles() async {
    try {
      final files = await UniversalFilePicker().pickFiles(
        options: const PickOptions(allowMultiple: true),
      );
      setState(() {
        _pickedFiles.clear();
        _pickedFiles.addAll(files);
      });
    } catch (e) {
      _showError('Failed to pick files: $e');
    }
  }

  Future<void> _pickImages() async {
    try {
      final files = await UniversalFilePicker().pickImages(
        allowMultiple: true,
        allowCamera: true,
      );
      setState(() {
        _pickedFiles.clear();
        _pickedFiles.addAll(files);
      });
    } catch (e) {
      _showError('Failed to pick images: $e');
    }
  }

  Future<void> _pickDocuments() async {
    try {
      final files = await UniversalFilePicker().pickDocuments(
        allowMultiple: true,
        extensions: ['pdf', 'doc', 'docx', 'txt', 'json'],
      );
      setState(() {
        _pickedFiles.clear();
        _pickedFiles.addAll(files);
      });
    } catch (e) {
      _showError('Failed to pick documents: $e');
    }
  }

  Future<void> _pickWithCamera() async {
    try {
      final files = await UniversalFilePicker().pickImages(
        allowMultiple: false,
        allowCamera: true,
      );
      setState(() {
        _pickedFiles.clear();
        _pickedFiles.addAll(files);
      });
    } catch (e) {
      _showError('Failed to capture image: $e');
    }
  }

  Future<void> _uploadFiles() async {
    if (_pickedFiles.isEmpty) return;

    setState(() {
      _isUploading = true;
      _uploadProgress = 0.0;
      _uploadResults.clear();
    });

    try {
      final uploader = UFileUploader();

      final results = await uploader.uploadBatchWithProgress(
        files: _pickedFiles,
        options: const UploadOptions(
          url: 'https://httpbin.org/post', // Test endpoint
          fieldName: 'file',
        ),
        onProgress: (current, total, progress) {
          setState(() {
            _uploadProgress = progress;
          });
        },
      );

      setState(() {
        _uploadResults.addAll(results);
        _isUploading = false;
        _uploadProgress = 1.0;
      });
    } catch (e) {
      setState(() {
        _uploadResults.add(
          UploadResult.failure(
            error: e.toString(),
            bytesUploaded: 0,
          ),
        );
        _isUploading = false;
      });
    }
  }

  void _cancelUpload() {
    // In a real implementation, you'd cancel the upload here
    setState(() {
      _isUploading = false;
    });
  }

  IconData _getFileIcon(String mimeType) {
    if (mimeType.startsWith('image/')) {
      return Icons.image;
    } else if (mimeType == 'application/pdf') {
      return Icons.picture_as_pdf;
    } else if (mimeType.startsWith('text/') || mimeType == 'application/json') {
      return Icons.description;
    } else if (mimeType.startsWith('video/')) {
      return Icons.video_file;
    } else if (mimeType.startsWith('audio/')) {
      return Icons.audio_file;
    } else {
      return Icons.insert_drive_file;
    }
  }

  void _showError(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
      ),
    );
  }
}
0
likes
160
points
32
downloads

Publisher

unverified uploader

Weekly Downloads

A cross-platform Flutter package for picking, previewing, and uploading files with a single, consistent API across Android, iOS, Web, macOS, Windows, and Linux.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

file_selector, flutter, http, image_picker

More

Packages that depend on universal_file_picker