π₯ firebase_uploader_plus
All-in-One Firebase File & Metadata Uploader
A powerful, customizable Flutter widget that handles file uploads to Firebase Storage with automatic Firestore metadata management, real-time streams, and comprehensive file management capabilities.
β Built On
firebase_storage- File upload and storagecloud_firestore- Metadata management and real-time streamsfile_picker- File selection from deviceimage_picker- Camera capture and gallery selectionfirebase_core- Firebase initializationfirebase_auth- User authentication (optional)
π‘ Features
πΌ Upload Features
- π Upload any file type (images, PDFs, videos, documents)
- π· Capture or pick images via camera/gallery
- π§ Smart auto-pathing (
uploads/users/{uid}/{timestamp}_{filename}) - π Real-time progress bar during upload
- π You can retry failed uploads from your own code using the callbacks
- β Success and failure callbacks
- π File size and type validation
- π¦ Multiple file upload support
π Metadata Features (Firestore Integration)
- β Auto-create Firestore document alongside each file:
{
"fileName": "receipt_123.pdf",
"downloadUrl": "https://firebasestorage...",
"uploadedBy": "user_uid",
"timestamp": "2024-01-15T10:30:00Z",
"fileType": "pdf",
"fileSizeBytes": 1363148,
"storagePath": "uploads/users/uid/1705315800000_receipt_123.pdf",
"isDeleted": false,
"customMetadata": {}
}
- π Update/delete metadata in Firestore
- ποΈ Soft-delete functionality
- π Upload statistics and analytics
π Stream Features
- π Real-time list of uploaded files using Firestore streams
- π₯ Auto-refresh UI on new uploads or deletions
- ποΈ Delete files from both Firebase Storage and Firestore
- π Search and filter capabilities
- π Pagination support for large file lists
π Getting Started
Requirements
- Dart
>=3.11.0 - Flutter
>=3.41.7
Installation
Add to your pubspec.yaml:
dependencies:
firebase_uploader_plus: ^1.1.0
Firebase Setup
- Initialize Firebase in your app:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
- Configure Firebase Storage rules:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
- Configure Firestore security rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /user_uploads/{document} {
allow read, write: if request.auth != null
&& resource.data.uploadedBy == request.auth.uid;
}
}
}
Basic Usage
import 'package:firebase_uploader_plus/firebase_uploader_plus.dart';
FirebaseUploader(
allowedExtensions: ['jpg', 'png', 'pdf'],
firestorePath: 'user_uploads',
firebaseStoragePath: 'uploads/users',
enablePreview: true,
onUploadComplete: (UploadMetadata metadata) {
print("File uploaded: ${metadata.downloadUrl}");
},
)
π Comprehensive Examples
Image Gallery with Camera Support
FirebaseUploader(
allowedExtensions: ['jpg', 'jpeg', 'png', 'gif'],
firestorePath: 'photos',
firebaseStoragePath: 'uploads/photos',
enablePreview: true,
enableCamera: true,
enableMultipleFiles: true,
maxFileSize: 5 * 1024 * 1024, // 5MB
onUploadComplete: (metadata) {
print('Photo uploaded: ${metadata.fileName}');
},
onUploadError: (error) {
print('Upload failed: $error');
},
)
Document Management System
FirebaseUploader(
allowedExtensions: ['pdf', 'doc', 'docx', 'txt'],
firestorePath: 'documents',
firebaseStoragePath: 'uploads/documents',
enablePreview: false,
enableCamera: false,
maxFileSize: 25 * 1024 * 1024, // 25MB
customMetadata: {
'department': 'HR',
'category': 'employee_docs',
},
fileTileBuilder: (context, upload) => ListTile(
leading: Icon(Icons.description),
title: Text(upload.fileName),
subtitle: Text(upload.formattedFileSize),
trailing: IconButton(
icon: Icon(Icons.download),
onPressed: () => downloadFile(upload.downloadUrl),
),
),
)
Custom Header and Empty State
FirebaseUploader(
firestorePath: 'media_uploads',
firebaseStoragePath: 'uploads/media',
headerBuilder: (context) => Container(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('Media Upload Center',
style: Theme.of(context).textTheme.headlineSmall),
SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () => pickFromGallery(),
icon: Icon(Icons.photo_library),
label: Text('Gallery'),
),
),
SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: () => capturePhoto(),
icon: Icon(Icons.camera_alt),
label: Text('Camera'),
),
),
],
),
],
),
),
emptyStateBuilder: (context) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.cloud_upload, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('No files uploaded yet'),
Text('Tap above to upload your first file'),
],
),
),
)
π§ API Reference
FirebaseUploader Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
allowedExtensions |
List<String> |
[] |
Allowed file extensions (empty = all types) |
firestorePath |
String |
required | Firestore collection path for metadata |
firebaseStoragePath |
String |
required | Firebase Storage path for files |
enablePreview |
bool |
true |
Enable file preview functionality |
enableCamera |
bool |
true |
Show camera capture button |
enableMultipleFiles |
bool |
false |
Allow multiple file selection |
filterByCurrentUser |
bool |
true |
When true, only lists files where uploadedBy matches the signed-in user. When false, lists all documents in the collection (no uploadedBy filter); use only with paths and security rules you trust. |
maxFileSize |
int? |
null |
Maximum file size in bytes |
onUploadComplete |
Function(UploadMetadata)? |
null |
Callback on successful upload |
onUploadError |
Function(String)? |
null |
Callback on upload error |
onUploadProgress |
Function(double)? |
null |
Progress callback (0.0 to 1.0) |
fileTileBuilder |
Widget Function(BuildContext, UploadMetadata)? |
null |
Custom file tile builder |
headerBuilder |
Widget Function(BuildContext)? |
null |
Custom header builder |
emptyStateBuilder |
Widget Function(BuildContext)? |
null |
Custom empty state builder |
customMetadata |
Map<String, String>? |
null |
Additional metadata to store |
UploadMetadata Properties
class UploadMetadata {
final String id; // Firestore document ID
final String fileName; // Original filename
final String downloadUrl; // Firebase Storage download URL
final String uploadedBy; // User ID who uploaded
final DateTime timestamp; // Upload timestamp
final String fileType; // File type (image, pdf, document, etc.)
final int fileSizeBytes; // File size in bytes
final String storagePath; // Firebase Storage path
final bool isDeleted; // Soft delete flag
final Map<String, dynamic>? customMetadata; // Additional metadata
// Computed properties
String get formattedFileSize; // Human-readable file size
String get fileExtension; // File extension
bool get isImage; // Is image file
bool get isPdf; // Is PDF file
bool get isVideo; // Is video file
bool get isDocument; // Is document file
}
π Authentication Support
The package automatically integrates with Firebase Auth:
// Files are automatically tagged with current user ID
FirebaseAuth.instance.currentUser?.uid
// Filter files by current user (default behavior)
FirebaseUploader(
filterByCurrentUser: true, // default
firestorePath: 'user_uploads',
firebaseStoragePath: 'uploads/users',
)
// Show all files (admin view)
FirebaseUploader(
filterByCurrentUser: false,
firestorePath: 'all_uploads',
firebaseStoragePath: 'uploads/shared',
)
π Offline Handling
The package includes built-in connectivity checking:
- Validates internet connection before starting an upload
- Surfaces an error via
onUploadErrorwhen the device appears offline
It does not automatically retry uploads when connectivity returns; handle retries in your app if you need them.
π¨ Customization
Custom File Tile
fileTileBuilder: (context, upload) => Card(
child: ListTile(
leading: CircleAvatar(
backgroundImage: upload.isImage
? NetworkImage(upload.downloadUrl)
: null,
child: upload.isImage ? null : Icon(Icons.description),
),
title: Text(upload.fileName),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${upload.formattedFileSize} β’ ${upload.fileType}'),
Text('Uploaded ${timeAgo(upload.timestamp)}'),
],
),
trailing: PopupMenuButton<String>(
onSelected: (action) => handleFileAction(action, upload),
itemBuilder: (context) => [
PopupMenuItem(value: 'download', child: Text('Download')),
PopupMenuItem(value: 'share', child: Text('Share')),
PopupMenuItem(value: 'delete', child: Text('Delete')),
],
),
),
)
Custom Upload Progress
onUploadProgress: (progress) {
setState(() {
uploadProgress = progress;
});
print('Upload progress: ${(progress * 100).toStringAsFixed(1)}%');
}
π Advanced Features
Stream Management
Use the services directly for advanced operations:
// Stream files by type
StreamBuilder<List<UploadMetadata>>(
stream: FirestoreService.streamUploadsByType(
firestorePath: 'uploads',
fileType: 'image',
limit: 50,
),
builder: (context, snapshot) {
final images = snapshot.data ?? [];
return GridView.builder(
itemCount: images.length,
itemBuilder: (context, index) => ImageTile(images[index]),
);
},
)
// Get upload statistics
final stats = await FirestoreService.getUploadStats(
firestorePath: 'user_uploads',
filterByUser: FirebaseAuth.instance.currentUser?.uid,
);
print('Total files: ${stats['totalFiles']}');
print('Total size: ${stats['totalSizeFormatted']}');
Batch Operations
// Batch delete multiple files
await FirestoreService.batchUpdateMetadata(
firestorePath: 'uploads',
documentIds: selectedFileIds,
updates: {'isDeleted': true, 'deletedAt': Timestamp.now()},
);
Custom Path Building
// Custom storage path
final customPath = PathBuilder.buildStoragePath(
basePath: 'company_files',
fileName: 'document.pdf',
userId: 'specific_user_id',
includeTimestamp: true,
);
// Result: company_files/specific_user_id/1705315800000_document.pdf
// Date-organized path
final datePath = PathBuilder.buildDateOrganizedPath(
basePath: 'daily_reports',
date: DateTime.now(),
);
// Result: daily_reports/2024/01/15
π§ Direct Service Usage
For advanced use cases, use the services directly:
// Upload file programmatically
final metadata = await FirebaseStorageHelper.uploadFile(
file: selectedFile,
storagePath: 'custom/path',
onProgress: (progress) => print('Progress: $progress'),
customMetadata: {'source': 'api_upload'},
);
// Save metadata to Firestore
final docId = await FirestoreService.saveUploadMetadata(
metadata: metadata,
firestorePath: 'api_uploads',
);
// Stream real-time updates
FirestoreService.streamUploads(
firestorePath: 'uploads',
filterByUser: FirebaseAuth.instance.currentUser?.uid, // omit / null = no user filter
orderBy: 'timestamp',
descending: true,
limit: 20,
).listen((uploads) {
print('${uploads.length} files available');
});
filterByUser: pass a UID to restrict to that user. Pass null to load all usersβ uploads (for example an admin dashboard). This matches FirebaseUploader(filterByCurrentUser: false), which passes null internally.
π Security Best Practices
- Firebase Storage Rules: Ensure users can only access their own files
- Firestore Rules: Implement proper read/write permissions
- File Validation: Always validate file types and sizes
- Authentication: Require authentication for sensitive uploads
- Content Scanning: Consider implementing virus/malware scanning
π Performance Tips
- Image Optimization: Compress images before upload
- Lazy Loading: Use pagination for large file lists
- Caching: Implement proper image caching strategies
- Background Upload: Handle uploads in background for large files
- Connection Monitoring: Check connectivity before operations
π± Platform Support
The widget uses dart:io File for picked paths, so it is aimed at Android, iOS, and desktop (macOS, Windows, Linux). Web is not supported by the current implementation without replacing file handling (for example XFile / bytes APIs and conditional imports).
π€ Contributing
- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Create a Pull Request
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Support
For issues and questions:
π Changelog
See CHANGELOG.md for detailed version history.
π Support This Package
If you find this package useful, consider supporting my work: