"Needed to wait for multiple async tasks in Flutter — BLoC events, downloads, DB inserts — and that's when I discovered the power of Completer."
Overview
In Flutter development, we often trigger multiple Future-based tasks — from API
calls, database writes, to Cubit events — but we don't always have a clean way to know
exactly when all of them are done.
Enter Completer — Dart's most powerful tool for manual control over async flow.
Why Use Completer?
- Flutter's Cubit/BLoC events usually return
void - You can't
awaitmultiple functions if they don't return aFuture - You often need to show a loader or dialog only after everything is done
- You want custom control after async logic completes
Benefits of Completer
- Works with any function, not just Cubit
- Combine multiple async events and wait cleanly
- Trigger logic after animation, file parsing, user action, API chain, etc.
- Build robust flows in large-scale Flutter apps
- Testable, clean, and scalable
Top Use Cases
| Use Case | Description |
|---|---|
| Wait for Cubit/BLoC event | Show toast, move to next screen after completion |
| Wait for multiple async functions | Download district, ward, vdc data in parallel |
| Coordinate after isolate/task completes | Background parsing or long file operations |
| Show loader until all actions done | Prevent double tap, ensure clean UX |
| Bridge non-Future APIs | Turn callbacks into awaitable logic |
| Trigger UI actions post-completion | Show dialog, navigate, unlock interaction |
Real Example: Wait for Multiple Async Events
Step 1 — Use Completer in your Cubit or service
download_cubit.dartvoid download({required Completer<void> completer}) async { try { await fetchFromApi(); await saveToLocalDB(); completer.complete(); } catch (e) { completer.completeError(e); } }
Step 2 — Trigger in UI and wait
download_page.dartfinal cubit1 = Completer<void>(); final cubit2 = Completer<void>(); final cubit3 = Completer<void>(); context.read<Cubit1>().download(completer: cubit1); context.read<Cubit2>().download(completer: cubit2); context.read<Cubit3>().download(completer: cubit3); Future.wait([ cubit1.future, cubit2.future, cubit3.future, ]).then((_) { showDialog(context: context, builder: (_) => SuccessDialog()); }).catchError((e) { showToast("❌ Failed: $e"); });
Reusable Utility: Wait for Multiple Completers
utils.dartFuture<void> waitForAll(List<Completer<void>> completers) { return Future.wait(completers.map((c) => c.future)); } // Usage final c1 = Completer<void>(); final c2 = Completer<void>(); myService1.download(completer: c1); myService2.upload(completer: c2); await waitForAll([c1, c2]);
Where to Use Completer
- Inside Cubit/BLoC void functions
- In async services, repositories, controllers
- Around isolate logic or parsing operations
- Inside UI flows waiting on multiple steps
- Custom logic layers that coordinate between APIs, DBs, or sensors
Final Thought
Think of Completer as your async completion notifier. It doesn't replace state management — it completes it.
Whether you're building enterprise-level apps, coordinating offline sync, or waiting for a background isolate, Completer gives you the full control you need.
🎯 Next time you're stuck waiting for something to finish — don't guess. Complete it.
By Rohan Kumar Chaudhary | Flutter Developer | Clean Code Advocate 🧼💙
Happy Coding! 💙