Completer: Manually Control a Future's Lifecycle
A `Completer` gives you a `Future` now but lets you decide when and how it finishes later. It's essential for converting old callback-based APIs into modern `async/await` code. The main footgun is trying to complete it more than once, which throws an error.
WHY IT EXISTS: Most of the time, async/await or Future constructors are sufficient for asynchronous tasks. However, sometimes the event that resolves a future comes from an external source that doesn't follow Dart's async model, like a native platform channel message or a C library callback. Completer was created to bridge this gap, giving you manual control to signal a future's completion from outside the normal flow of Dart code.
THE MENTAL MODEL: A Completer is like a promise issuer. You create a Completer, which immediately gives you a Future object—the promise. You can pass this Future around your application to any code that needs to wait for the result. The code that holds the Completer object itself has the exclusive power to fulfill the promise later by calling complete() with a value, or break it by calling completeError() with an error.
HOW IT WORKS: You instantiate a Completer<T>(). This object has two key parts. First is its .future property, which is a standard Future<T> you immediately return to the consumer. Second are its .complete(value) and .completeError(error) methods. You hold onto the Completer instance and call one of these methods when your asynchronous work is done. Once called, the Future you handed out earlier resolves with the value or error you provided. The Completer's job is then finished.
WHEN TO USE IT: The primary use case is wrapping non-Future-based asynchronous APIs. For example, if you're interacting with a native library that signals completion via a callback function, you would create a Completer, return its future, and then call completer.complete() from inside the callback function when it fires. This transforms a clunky callback pattern into a clean, await-able Future.
WHEN NOT TO USE IT: Avoid Completer when a simpler Future constructor or async/await will do. If your logic is self-contained within a single function that can be marked async, using a Completer adds unnecessary complexity and boilerplate. For simple sequential operations, chaining futures with .then() is also cleaner.
ONE CANONICAL EXAMPLE: Imagine wrapping a JavaScript API that uses callbacks in a Dart web app. You can write a function: Future<String> fetchUserData(String userId) { final completer = Completer<String>(); jsApi.fetch(userId, (error, data) { if (error != null) { completer.completeError(error); } else { completer.complete(data as String); } }); return completer.future; }. Now, other Dart code can simply call await fetchUserData('123'); without ever seeing the underlying callback mechanism.
Read the original → api.flutter.dev
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.