Upload arch
A comprehensive upload manager that integrates:
- Stream-based state notification via
UploadStateNotifier - Retry logic via
RetryFuture - Firebase Storage tasks for actual file uploads
Wraps together:
- Firebase
UploadTask(orDownloadTask) RetryFuturefor retry logic- Progress monitoring subscriptions
- Task metadata (ID, destination, sync/async mode)
Synchronous (Fire-and-Forget)
String taskId = manager.uploadSync(file, location: 'photos/');
// Returns immediately, upload continues in background
Asynchronous (Awaitable)
UploadResult result = await manager.uploadAsync(file, location: 'docs/');
// Blocks until complete, returns result
manager.notifier.stream.listen((state) {
print('Uploads: ${state.inProgressCount} active, ${state.completedCount} done');
});
- State Management:
UploadStateNotifiertracks queue state - Retry Logic:
RetryFuturehandles failures and network issues - Upload Execution: Firebase Storage handles actual uploads
- Coordination:
UploadTaskManagerorchestrates everything
- Mix sync and async uploads in the same application
- Monitor all uploads via single stream
- Cancel individual tasks or all tasks
- Handles user session changes (sign in/out)
- Network-aware retries (waits for connectivity)
- Exponential backoff for transient errors
- Proper resource cleanup
- Comprehensive error handling
- Stream-based state is easy to test
- Mock Firebase Storage for unit tests
- Observable state changes
- Deterministic behavior
User Action
↓
UploadTaskManager.uploadSync/uploadAsync
↓
Create TrackedUploadTask
├─→ Firebase UploadTask (actual upload)
├─→ RetryFuture (handles retries)
└─→ UploadStateNotifier (state tracking)
↓
Stream.broadcast()
↓
Multiple Listeners
├─→ UI Widgets (progress bars)
├─→ Notification Service (background alerts)
└─→ Analytics Service (tracking)
// Fire and forget
final taskId = uploadManager.uploadSync(photo, location: 'gallery/');
try {
final result = await uploadManager.uploadAsync(document, location: 'docs/');
if (result.isSuccess) {
showSuccess();
}
} on UploadException catch (e) {
showError(e.message);
}
StreamBuilder(
stream: uploadManager.notifier.stream,
builder: (context, snapshot) {
final state = snapshot.data;
return Text('${state.inProgressCount} uploads in progress');
},
)
// Critical upload: wait for it
await uploadManager.uploadAsync(profilePhoto, location: 'profile/');
// Background uploads: fire and forget
for (final photo in galleryPhotos) {
uploadManager.uploadSync(photo, location: 'gallery/');
}
- Test state transitions
- Verify stream emissions
- Mock Firebase Storage
- Test retry logic
- End-to-end upload flows
- Network failure scenarios
- Multiple concurrent uploads
- User session changes
test('uploadSync adds task to queue immediately', () async {
final taskId = manager.uploadSync(file, location: 'test/');
expect(taskId, isNotEmpty);
expect(manager.notifier.waitingCount, equals(1));
});
upload_task_manager.dart(456 lines)- Main manager class
- TrackedUploadTask wrapper
- Sync/async upload methods
- State management integration
upload_task_manager_integration_test.dart(332 lines)- Comprehensive test examples
- Demonstrates all usage patterns
- Shows sync vs async patterns
- Stream monitoring examples
UPLOAD_TASK_MANAGER_GUIDE.md(548 lines)- Complete usage guide
- Architecture diagrams
- Best practices
- Common patterns
- Troubleshooting
- ❌
state = statedidn’t trigger notifications - ❌ Tight coupling to Riverpod
- ❌ Complex listener management
- ❌ Limited to single notification pattern
- ✅ Explicit
_notify()always works - ✅ Works with any DI container
- ✅ Broadcast streams support multiple listeners
- ✅ Flexible sync/async patterns
- ✅ Better separation of concerns
- ✅ Navigation-independent (streams work everywhere)
- Memory: ~200 bytes per tracked task
- Concurrency: Supports unlimited queued tasks (limited by
kMaxUploadQueueSize) - Stream Overhead: Minimal (broadcast stream with no buffering)
- Retry Overhead: Only when failures occur
- Cleanup: Automatic disposal of completed/failed tasks
Possible additions:
- Priority Queue: Upload important files first
- Bandwidth Throttling: Limit upload speed
- Offline Queue: Persist uploads for retry after app restart
- Compression: Auto-compress before upload
- Batch Operations: Upload multiple files as atomic operation
- Progress Aggregation: Combined progress for multiple uploads
The stream-based UploadTaskManager provides a production-ready solution for Firebase Storage uploads with:
- Flexibility: Sync and async patterns
- Reliability: Automatic retries and error handling
- Observability: Real-time state streaming
- Maintainability: Clear separation of concerns
- Testability: Easy to mock and test
The integration successfully combines:
- Stream-based state notification
- Network-aware retry logic
- Firebase Storage tasks
- Clean API design
All working together to provide a robust upload management system! 🚀