UploadTaskManager Quick Reference

Setup

final manager = UploadTaskManager(
  storage: FirebaseStorage.instance,
  randomizer: DefaultRandomizer(),
  uid: currentUserId,
  container: providerContainer, // optional
);

Upload Methods

Sync (Fire-and-Forget)

String taskId = manager.uploadSync(file, location: 'photos/');
// Returns immediately, upload in background

Async (Awaitable)

UploadResult result = await manager.uploadAsync(file, location: 'docs/');
if (result.isSuccess) { /* success */ }

State Monitoring

Listen to All Uploads

manager.notifier.stream.listen((state) {
  print('Active: ${state.inProgressCount}');
  print('Done: ${state.completedCount}');
  print('Failed: ${state.failedCount}');
});

Get Current State

final state = manager.notifier.state;
print('Queue size: ${state.totalCount}');
print('Has errors: ${state.hasErrors}');

Task Management

Cancel Tasks

await manager.cancelTask(taskId);  // Cancel one
await manager.cancelAll();         // Cancel all

Clear Errors

manager.notifier.clearErrors();  // Remove failed tasks

Reset State

manager.notifier.reset();  // Clear everything

User Management

await manager.setUser(newUserId);  // Cancels old user's uploads

Disposal

await manager.dispose();  // Always call when done!

Common Patterns

Upload with Progress

manager.uploadAsync(
  file,
  location: 'photos/',
  onProgress: (snapshot) {
    double percent = snapshot.bytesTransferred / snapshot.totalBytes * 100;
    print('${percent.toStringAsFixed(1)}%');
  },
);

Batch Upload

for (final file in files) {
  manager.uploadSync(file, location: 'batch/');
}

Critical + Background

// Wait for critical
await manager.uploadAsync(profilePhoto, location: 'profile/');

// Fire-and-forget background
for (final photo in gallery) {
  manager.uploadSync(photo, location: 'gallery/');
}

Error Handling

try {
  final result = await manager.uploadAsync(file, location: 'docs/');
  // handle result
} on UploadException catch (e) {
  print('Upload failed: ${e.message}');
}

State Properties

state.waitingCount       // Tasks queued
state.inProgressCount    // Currently uploading
state.completedCount     // Successfully done
state.failedCount        // Failed uploads
state.totalCount         // All tasks
state.isFull            // Queue at capacity
state.hasErrors         // Any failures
state.errorCount        // Number of errors

Widget Integration

StreamBuilder(
  stream: uploadManager.notifier.stream,
  builder: (context, snapshot) {
    if (!snapshot.hasData) return Loading();

    final state = snapshot.data!;
    return Column(
      children: [
        Text('${state.inProgressCount} uploading'),
        LinearProgressIndicator(
          value: state.completedCount / state.totalCount,
        ),
      ],
    );
  },
)

Testing

test('sync upload returns task ID', () {
  final taskId = manager.uploadSync(file, location: 'test/');
  expect(taskId, isNotEmpty);
});

test('async upload returns result', () async {
  final result = await manager.uploadAsync(file, location: 'test/');
  expect(result.status, isNotNull);
});

Tips

Do:

  • Call dispose() when done
  • Use sync for background uploads
  • Use async for critical uploads
  • Monitor stream for UI updates
  • Handle errors gracefully

Don’t:

  • Forget to dispose
  • Block UI with sync uploads (they’re non-blocking)
  • Ignore error state
  • Upload without user authentication
  • Create multiple managers (use singleton)

Troubleshooting

Uploads not starting?

print('Ready: ${manager.isReady}');
print('Authenticated: ${manager.isAuthenticated}');

Stream not updating?

manager.notifier.stream.listen(
  (state) => print('Update: $state'),
  onError: (e) => print('Error: $e'),
  onDone: () => print('Stream closed'),
);

Memory leaks?

// Always dispose in lifecycle
@override
void dispose() {
  manager.dispose();
  super.dispose();
}
  • Full Guide: UPLOAD_TASK_MANAGER_GUIDE.md
  • Integration Details: INTEGRATION_SUMMARY.md
  • Tests: upload_task_manager_integration_test.dart