bosc_flutter_file_manager (1.0.0)
Installation
dart pub add bosc_flutter_file_manager:1.0.0 --hosted-url=About this package
Bosc Flutter File Manager 📤
A production-grade Flutter package for upload, download, and view file operations with multi-cloud storage support (AWS S3, Firebase Storage, Supabase Storage). Supports Android and iOS (and other platforms). Includes pre-signed URLs, automatic retry logic, offline queue management, and structured logging.
📱 Supported Platforms
- ✅ Android
- ✅ iOS
- ✅ Web, macOS, Windows, Linux (where applicable)
☁️ Supported Cloud Storage
- ✅ AWS S3 - Amazon's object storage (via pre-signed URLs)
- ✅ Firebase Storage - Google's Firebase cloud storage
- ✅ Supabase Storage - Open-source Firebase alternative
✨ Features
- ✅ Multi-Cloud Support - Switch between AWS, Firebase, and Supabase easily
- ✅ AWS S3 Pre-signed URL Support - Upload directly to S3 using pre-signed URLs
- ✅ Firebase Storage Integration - Native Firebase Storage support
- ✅ Supabase Storage Integration - Native Supabase Storage support
- ✅ Multiple Upload Methods - Pre-signed URL, API proxy, and direct uploads
- ✅ Automatic Retry Logic - Exponential backoff with configurable retry attempts
- ✅ Offline Queue Management - Queue uploads when offline, auto-resume when online
- ✅ Real-time Progress Tracking - Track upload progress with detailed callbacks
- ✅ File Validation - Validate file size, type, extension before upload
- ✅ Batch Upload Support - Upload multiple files with parallel/sequential options
- ✅ Cancellation Support - Cancel uploads at any time
- ✅ Checksum Validation - MD5/SHA256 checksum support
- ✅ Comprehensive Error Handling - Domain-specific exceptions with detailed error info
- ✅ Network Detection - Automatic connectivity monitoring
- ✅ Download & View - Download files from storage URLs and open them with the native app (Android/iOS)
- ✅ Structured Logging -
dart:developerlog integration; optional custom logger andenableLoggingon clients - ✅ Production Ready - Enterprise-grade code quality and architecture
📦 Installation
Add this to your package's pubspec.yaml file:
dependencies:
bosc_flutter_file_manager: ^1.0.0
Then run:
flutter pub get
🚀 Quick Start
Choose Your Storage Provider
AWS S3
import 'dart:io';
import 'package:bosc_flutter_file_manager/bosc_flutter_file_manager.dart';
final client = UniversalFileUploadClient(
config: UniversalUploadClientConfig(
storageConfig: AwsStorageConfig(
presignedUrlEndpoint: 'https://api.example.com/upload/presigned-url',
// Optional: auth headers for your backend pre-signed URL endpoint
presignedUrlHeaders: {
'Authorization': 'Bearer <token>',
},
),
),
);
Firebase Storage
final client = UniversalFileUploadClient(
config: UniversalUploadClientConfig(
storageConfig: FirebaseStorageConfig(
bucketName: 'your-bucket.appspot.com', // Optional
),
),
);
Supabase Storage
final client = UniversalFileUploadClient(
config: UniversalUploadClientConfig(
storageConfig: SupabaseStorageConfig(
supabaseUrl: 'https://your-project.supabase.co',
supabaseKey: 'your-anon-key',
bucketName: 'uploads',
),
),
);
Note: the current Supabase provider uses uploadBinary, which reads the full file into memory. For very large files, consider AWS pre-signed PUT uploads or a custom streaming implementation.
Upload a File
// Works the same for all storage providers!
try {
final response = await client.upload(
file: File('/path/to/file.pdf'),
path: 'documents/file.pdf', // Storage path
onProgress: (progress) {
print('Upload progress: ${(progress.percentage * 100).toStringAsFixed(1)}%');
},
);
if (response.success) {
print('✓ File uploaded: ${response.fileUrl}');
print(' ETag: ${response.eTag}');
print(' File Key: ${response.fileKey}');
}
} on ValidationException catch (e) {
print('✗ Validation failed: ${e.message}');
} on NoInternetException catch (e) {
print('✗ No internet connection');
} on UploadException catch (e) {
print('✗ Upload failed: ${e.message}');
}
Download a File
Download a file from a URL (e.g. from response.fileUrl) and optionally open it with the device’s default app (works on Android and iOS):
import 'package:bosc_flutter_file_manager/bosc_flutter_file_manager.dart';
// Download and open (e.g. PDF, images)
final result = await FileDownloadService.downloadAndOpen(
url: fileUrl, // from upload response or getFileUrl()
fileName: 'document.pdf',
openAfterDownload: true,
onProgress: (progress) {
print('Download: ${(progress * 100).toStringAsFixed(0)}%');
},
);
if (!result.success) print(result.message);
Download only (no open):
final localPath = await FileDownloadService.download(
url: fileUrl,
fileName: 'document.pdf',
onProgress: (progress) { /* ... */ },
);
View (Open) a File
Open a local file with the system default app (e.g. PDF viewer, image viewer) on Android and iOS:
import 'package:bosc_flutter_file_manager/bosc_flutter_file_manager.dart';
final result = await FileOpenerService.open('/path/to/local/file.pdf');
if (result.success) {
// File opened in external app
} else {
print(result.message); // e.g. "No app found to open this file type."
}
Get file URL from storage (for download/view):
final fileUrl = await client.getFileUrl('documents/file.pdf');
// Use fileUrl with FileDownloadService.downloadAndOpen or download
Enable Logging
Use structured logging (e.g. in Flutter DevTools) or a custom logger:
final client = UniversalFileUploadClient(
config: UniversalUploadClientConfig(
storageConfig: yourStorageConfig,
enableLogging: true,
// Optional: custom logger
// logger: (msg) => myLogger.info(msg),
),
);
Upload with File Validation
// Configure validation for images only
final client = AwsFileUploadClient(
config: UploadClientConfig(
validationConfig: FileValidationConfig.images(maxSizeMB: 10),
),
);
// Upload will automatically validate before uploading
final response = await client.upload(
file: File('/path/to/image.jpg'),
onProgress: (progress) {
print('${progress.progressString}');
},
);
Batch Upload
final files = [
File('/path/to/file1.pdf'),
File('/path/to/file2.pdf'),
File('/path/to/file3.pdf'),
];
final requests = files.map((file) => UploadRequest(
file: file,
folder: 'documents',
presignedUrlEndpoint: 'https://api.example.com/upload/presigned-url',
)).toList();
final batchResponse = await client.uploadBatch(
requests,
parallel: true,
maxConcurrent: 3,
onProgress: (index, progress) {
print('File $index: ${(progress.percentage * 100).toStringAsFixed(1)}%');
},
);
print('✓ Uploaded ${batchResponse.successCount} files');
print('✗ Failed ${batchResponse.failureCount} files');
📚 Storage Provider Guide
See MULTI_CLOUD_GUIDE.md for detailed documentation on each storage provider.
AWS S3 (Pre-signed URL Flow)
The most common pattern for AWS S3 uploads. Your backend generates a pre-signed URL, and the client uploads directly to S3.
final client = UniversalFileUploadClient(
config: UniversalUploadClientConfig(
storageConfig: AwsStorageConfig(
presignedUrlEndpoint: 'https://api.example.com/upload/presigned-url',
),
),
);
final response = await client.upload(
file: File('/path/to/file.pdf'),
path: 'uploads/document.pdf',
);
Backend Contract Example:
// POST https://api.example.com/upload/presigned-url
// Request:
{
"fileName": "document.pdf",
"folder": "uploads",
"contentType": "application/pdf"
}
// Response:
{
"uploadUrl": "https://bucket.s3.amazonaws.com/uploads/document.pdf?X-Amz-Algorithm=...",
"method": "PUT",
"headers": {
"Content-Type": "application/pdf"
},
"fileKey": "uploads/document.pdf"
}
2. API Proxy Upload
Upload file to your backend API, which handles the S3 upload internally.
final response = await client.upload(
file: File('/path/to/file.pdf'),
method: UploadMethod.apiProxy,
presignedUrlEndpoint: 'https://api.example.com/upload',
);
3. Direct Upload (with Pre-signed URL)
If you already have a pre-signed URL, use it directly:
final response = await client.upload(
file: File('/path/to/file.pdf'),
directUploadUrl: 'https://bucket.s3.amazonaws.com/...',
httpMethod: UploadHttpMethod.put,
);
🔄 Offline Queue Management
Handle uploads when the device is offline:
// NOTE: Offline queue currently supports AwsFileUploadClient flows (AWS pre-signed URL / API proxy).
final awsClient = AwsFileUploadClient(
config: UploadClientConfig(
defaultPresignedUrlEndpoint: 'https://api.example.com/upload/presigned-url',
),
);
// Initialize queue manager
final queueManager = UploadQueueManager(
client: awsClient,
config: QueueConfig(
maxConcurrent: 3,
autoStart: true,
autoRetry: true,
),
);
// Enqueue uploads
final uploadId = queueManager.enqueue(
request: UploadRequest(
file: File('/path/to/file.pdf'),
presignedUrlEndpoint: 'https://api.example.com/upload/presigned-url',
),
priority: 10, // Higher priority uploads first
);
// Listen to queue updates
queueManager.onQueueUpdate.listen((uploads) {
final status = queueManager.getStatus();
print('Queue: ${status.pending} pending, ${status.uploading} uploading');
});
// Listen to individual upload status
queueManager.onStatusChange.listen((upload) {
print('Upload ${upload.id}: ${upload.status}');
if (upload.progress != null) {
print('Progress: ${upload.progress!.progressString}');
}
});
// Control the queue
queueManager.pause(); // Pause uploads
queueManager.start(); // Resume uploads
queueManager.retry(uploadId); // Retry failed upload
queueManager.cancel(uploadId); // Cancel upload
// Cleanup
queueManager.dispose();
⚙️ Advanced Configuration
Custom Validation
final validator = FileValidator(
FileValidationConfig(
maxFileSize: 50 * 1024 * 1024, // 50 MB
allowedMimeTypes: ['application/pdf', 'image/*'],
allowedExtensions: ['.pdf', '.jpg', '.png'],
allowEmptyFiles: false,
minFileSize: 1024, // 1 KB minimum
fileNamePattern: RegExp(r'^[a-zA-Z0-9_-]+\.[a-z]+$'),
),
);
// Validate before upload
await validator.validate(file);
Pre-configured Validators
// Images only (10 MB max)
FileValidationConfig.images(maxSizeMB: 10)
// Documents only (25 MB max)
FileValidationConfig.documents(maxSizeMB: 25)
// Videos only (500 MB max)
FileValidationConfig.videos(maxSizeMB: 500)
Retry Configuration
final client = AwsFileUploadClient(
config: UploadClientConfig(
retryConfig: RetryConfig(
maxAttempts: 5,
initialDelay: Duration(seconds: 1),
maxDelay: Duration(seconds: 30),
backoffMultiplier: 2.0,
enableJitter: true,
retryOnTimeout: true,
retryOn5xx: true,
retryOnNetworkError: true,
),
),
);
Upload with Custom Headers & Metadata
final response = await client.upload(
file: File('/path/to/file.pdf'),
customHeaders: {
'Authorization': 'Bearer $token',
'X-Custom-Header': 'value',
},
metadata: {
'userId': '12345',
'category': 'documents',
},
includeMd5Checksum: true,
);
Cancel Upload
final cancelToken = CancelToken();
// Start upload
final uploadFuture = client.upload(
file: File('/path/to/large-file.mp4'),
cancelToken: cancelToken,
onProgress: (progress) {
if (shouldCancel) {
cancelToken.cancel('User cancelled');
}
},
);
try {
await uploadFuture;
} on UploadCancelledException {
print('Upload was cancelled');
}
🎯 Error Handling
The library provides comprehensive error handling with specific exception types:
try {
await client.upload(file: file);
} on FileNotFoundException catch (e) {
// File doesn't exist
print('File not found: ${e.message}');
} on PermissionException catch (e) {
// Can't read file
print('Permission denied: ${e.message}');
} on ValidationException catch (e) {
// File validation failed
print('Validation error: ${e.message}');
print('Error type: ${e.errorType}');
} on NoInternetException catch (e) {
// No internet connection
print('No internet: ${e.message}');
} on UploadTimeoutException catch (e) {
// Upload timed out
print('Timeout after ${e.timeout.inSeconds}s');
} on AwsRejectedException catch (e) {
// AWS/S3 rejected the upload
print('AWS error: ${e.message}');
print('Status code: ${e.statusCode}');
} on PresignedUrlException catch (e) {
// Failed to get pre-signed URL
print('Pre-signed URL error: ${e.message}');
} on RetryExhaustedException catch (e) {
// All retries failed
print('Failed after ${e.attempts} attempts');
} on NetworkException catch (e) {
// Generic network error
print('Network error: ${e.message}');
} on UploadException catch (e) {
// Base exception (catch-all)
print('Upload error: ${e.message}');
}
🏗️ Architecture
The library follows clean architecture principles with clear separation of concerns:
lib/
├── src/
│ ├── client/
│ │ └── aws_file_upload_client.dart # Main client
│ ├── services/
│ │ ├── upload_service.dart # Core upload logic
│ │ └── retry_handler.dart # Retry mechanism
│ ├── models/
│ │ ├── upload_request.dart # Request model
│ │ ├── upload_response.dart # Response model
│ │ ├── upload_progress.dart # Progress tracking
│ │ └── batch_upload_result.dart # Batch results
│ ├── validators/
│ │ └── file_validator.dart # File validation
│ ├── exceptions/
│ │ └── upload_exception.dart # Custom exceptions
│ ├── queue/
│ │ └── upload_queue_manager.dart # Queue management
│ └── utils/
│ └── network_info.dart # Network detection
└── bosc_flutter_file_manager.dart # Public exports
🔒 Security Best Practices
- Never log sensitive data - The library sanitizes logs by default
- Use HTTPS only - Always use secure endpoints
- Validate on both client and server - Client validation is for UX, server validation is for security
- Use short-lived pre-signed URLs - Set appropriate expiration times
- Implement rate limiting - Prevent abuse of your upload endpoints
- Verify checksums - Use MD5/SHA256 checksums to ensure data integrity
⚠️ Common Pitfalls with AWS S3
1. CORS Configuration
Ensure your S3 bucket has proper CORS configuration:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["PUT", "POST"],
"AllowedOrigins": ["*"],
"ExposeHeaders": ["ETag"]
}
]
2. Content-Type Mismatch
The Content-Type in the pre-signed URL must match the upload:
// Backend should return the same Content-Type
await client.upload(
file: file,
contentType: 'application/pdf', // Must match backend
);
3. Pre-signed URL Expiration
Handle expired URLs gracefully:
try {
await client.upload(file: file);
} on AwsRejectedException catch (e) {
if (e.statusCode == 403) {
// URL might be expired, retry with new URL
}
}
4. File Size Limits
AWS S3 has a 5GB limit for single PUT operations. For larger files, use multipart upload:
await client.upload(
file: largeFile,
enableChunkedTransfer: true,
chunkSize: 5 * 1024 * 1024, // 5 MB chunks
);
🧪 Testing
Run the test suite:
flutter test
The package includes comprehensive unit tests for all components.
📝 Example Backend Implementation
Node.js + Express + AWS SDK
const express = require('express');
const AWS = require('aws-sdk');
const { v4: uuidv4 } = require('uuid');
const s3 = new AWS.S3();
const app = express();
app.post('/upload/presigned-url', async (req, res) => {
const { fileName, folder, contentType } = req.body;
const fileKey = `${folder}/${uuidv4()}-${fileName}`;
const params = {
Bucket: 'your-bucket-name',
Key: fileKey,
ContentType: contentType,
Expires: 300, // 5 minutes
};
try {
const uploadUrl = await s3.getSignedUrlPromise('putObject', params);
res.json({
uploadUrl,
method: 'PUT',
headers: {
'Content-Type': contentType,
},
fileKey,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000);
🤝 Contributing
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Built with Dio for HTTP requests
- Uses connectivity_plus for network detection
- Inspired by AWS SDK patterns
📞 Support
- 🐛 Issues: GitHub Issues
- 📦 Package: GitHub Repository
Made with ❤️ by the Flutter community