Kotlin
Dart
Completer
Asynchronous Programming
Future

Is there anyway in Kotlin something like Dart's Completer behavior?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Kotlin's equivalent to Dart's Completer is CompletableDeferred. Both serve the same purpose — they create a promise/future that you can complete manually from outside the asynchronous computation. In Dart, Completer wraps a Future and exposes complete() and completeError() methods. In Kotlin, CompletableDeferred extends Deferred and exposes complete() and completeExceptionally(). The underlying concept is identical, but the APIs follow each language's concurrency conventions.

Dart's Completer

dart
1import 'dart:async';
2
3Future<String> fetchData() {
4  final completer = Completer<String>();
5
6  // Simulate async work
7  Timer(Duration(seconds: 2), () {
8    completer.complete('Data loaded');
9  });
10
11  return completer.future;
12}
13
14void main() async {
15  final result = await fetchData();
16  print(result); // Data loaded
17}

Dart's Completer creates a Future that you resolve manually by calling complete() or completeError(). This is useful when wrapping callback-based APIs into future-based ones.

Kotlin's CompletableDeferred

kotlin
1import kotlinx.coroutines.*
2
3suspend fun fetchData(): String {
4    val deferred = CompletableDeferred<String>()
5
6    // Simulate async work (e.g., callback-based API)
7    GlobalScope.launch {
8        delay(2000)
9        deferred.complete("Data loaded")
10    }
11
12    return deferred.await()
13}
14
15fun main() = runBlocking {
16    val result = fetchData()
17    println(result) // Data loaded
18}

CompletableDeferred<T> is the direct Kotlin equivalent. Call complete(value) to resolve it or completeExceptionally(exception) to reject it. Consumers call await() to suspend until the value is available.

Wrapping Callback APIs

kotlin
1import kotlinx.coroutines.*
2
3// Simulated callback-based API
4fun loadFromNetwork(callback: (Result<String>) -> Unit) {
5    Thread {
6        Thread.sleep(1000)
7        callback(Result.success("Response body"))
8    }.start()
9}
10
11// Wrap with CompletableDeferred
12suspend fun loadFromNetworkAsync(): String {
13    val deferred = CompletableDeferred<String>()
14
15    loadFromNetwork { result ->
16        result.onSuccess { deferred.complete(it) }
17        result.onFailure { deferred.completeExceptionally(it) }
18    }
19
20    return deferred.await()
21}
22
23fun main() = runBlocking {
24    val data = loadFromNetworkAsync()
25    println(data) // Response body
26}

This is the most common use case — converting a callback-based API into a suspending function. The CompletableDeferred bridges the gap between callback and coroutine worlds.

suspendCancellableCoroutine (Preferred Alternative)

kotlin
1import kotlinx.coroutines.*
2import kotlin.coroutines.resume
3import kotlin.coroutines.resumeWithException
4
5suspend fun loadFromNetworkSuspend(): String =
6    suspendCancellableCoroutine { cont ->
7        loadFromNetwork { result ->
8            result.onSuccess { cont.resume(it) }
9            result.onFailure { cont.resumeWithException(it) }
10        }
11    }

For wrapping callbacks, suspendCancellableCoroutine is often preferred over CompletableDeferred because it integrates directly with structured concurrency and supports cancellation out of the box. Use CompletableDeferred when you need to pass the deferred object around or complete it from a different scope.

Error Handling

kotlin
1import kotlinx.coroutines.*
2
3fun main() = runBlocking {
4    val deferred = CompletableDeferred<String>()
5
6    launch {
7        delay(500)
8        deferred.completeExceptionally(RuntimeException("Network error"))
9    }
10
11    try {
12        val result = deferred.await()
13        println(result)
14    } catch (e: RuntimeException) {
15        println("Error: ${e.message}") // Error: Network error
16    }
17}

completeExceptionally() is the Kotlin equivalent of Dart's completeError(). The exception is thrown at the await() call site, where it can be caught with try-catch.

One-Shot Event Channel

kotlin
1import kotlinx.coroutines.*
2
3class OneTimeEvent<T> {
4    private val deferred = CompletableDeferred<T>()
5
6    fun emit(value: T) {
7        deferred.complete(value)
8    }
9
10    suspend fun await(): T = deferred.await()
11
12    val isCompleted: Boolean get() = deferred.isCompleted
13}
14
15fun main() = runBlocking {
16    val loginComplete = OneTimeEvent<String>()
17
18    launch {
19        println("Waiting for login...")
20        val userId = loginComplete.await()
21        println("Logged in as $userId")
22    }
23
24    delay(1000)
25    loginComplete.emit("user-42")
26}

CompletableDeferred naturally models one-shot events — something that happens exactly once and multiple consumers can await the same result.

Side-by-Side Comparison

FeatureDart CompleterKotlin CompletableDeferred
CreateCompleter<T>()CompletableDeferred<T>()
Get awaitable.future(itself is Deferred)
Resolve.complete(value).complete(value)
Reject.completeError(error).completeExceptionally(exception)
Awaitawait completer.futuredeferred.await()
Check completed.isCompleted.isCompleted
CancelNot built-in.cancel()
Multiple completionsError on second callReturns false on second call

Common Pitfalls

  • Completing twice: In Dart, calling complete() twice throws a StateError. In Kotlin, complete() returns false silently if already completed. Check isCompleted first if you need to know whether the completion succeeded.
  • Memory leaks from uncompleted Deferreds: If you create a CompletableDeferred and never call complete() or completeExceptionally(), any coroutine awaiting it will suspend forever. Always ensure a completion path exists, especially in error scenarios.
  • Using CompletableDeferred when suspendCancellableCoroutine suffices: For simple callback wrapping, suspendCancellableCoroutine is cleaner and handles cancellation automatically. Reserve CompletableDeferred for cases where you need to pass the deferred reference between components.
  • GlobalScope.launch for completion: Launching completion work in GlobalScope breaks structured concurrency. Use the parent scope or pass a CoroutineScope to ensure proper lifecycle management.
  • Forgetting cancellation: CompletableDeferred supports cancel(), but if the underlying callback API does not support cancellation, the callback may still fire after cancellation. Guard with isActive or isCompleted checks.

Summary

  • Kotlin's CompletableDeferred<T> is the direct equivalent of Dart's Completer<T>
  • Use complete() and completeExceptionally() to resolve or reject the deferred value
  • Consumers call await() to suspend until the value is available
  • For simple callback wrapping, prefer suspendCancellableCoroutine over CompletableDeferred
  • CompletableDeferred excels when the deferred reference needs to be shared across components
  • Unlike Dart, Kotlin's CompletableDeferred supports cancellation natively through cancel()

Course illustration
Course illustration

All Rights Reserved.