Kotlin Coroutines as the Missing Adapter for TDLib's Async Callbacks
Callback-heavy SDKs like TDLib become tractable when a suspend adapter linearizes the flow, freeing business code from nested callbacks and manual latch counting. The suspend-vs-Flow boundary drawn here is a reusable rule for any async library that mixes one-shot requests with event streams.
TDLib's Java API is callback-driven: every request takes a ResultHandler, and multi-step business flows quickly spiral into nested callbacks or force you to block threads with CountDownLatch. This retrospective from a Telegram data project walks through a third option: a thin adapter layer that converts each one-shot request into a suspend function using suspendCoroutine. The coroutine suspends, not the thread, so the calling thread is free while the callback fires. Once wrapped, sequential workflows like "join group then fetch chat details" read as plain sequential code, and pagination collapses into a while loop that advances a from-message-id cursor.
Concurrency also stops requiring manual counting. Instead of CountDownLatch, the code uses async and awaitAll to launch user profile lookups and file downloads in parallel, with a Semaphore(10) to cap concurrent requests. The article is careful to distinguish one-shot requests from continuous events: TDLib's Updates should not be forced into suspend, they map naturally to Flow via callbackFlow.
The broader claim is that coroutines do not change the async nature of TDLib, they only linearize the expression of async operations. The async library stays asynchronous, and the adapter becomes the only place that touches callbacks. That separation of concerns is the design pattern that makes the whole thing work, not the coroutine mechanisms themselves.
The adapter layer is the real architecture; coroutines are just syntax. Once callbacks are confined to a single adapter, business logic can be written and reviewed linearly.
Using CountDownLatch to simulate sync behavior preserves the callback signature but punishes performance by holding threads, a common Java reflex that coroutines sidestep without changing the underlying async library.
The distinction between suspend for one-shot operations and Flow for continuous streams is a general principle for any event-driven SDK; forcing Updates into suspend would create an awkward API that never ends.
The article's claim that "coroutines are a linearized expression of async callbacks" shifts focus from coroutines as a concurrency tool to coroutines as an organizational tool, a more useful mental model for most business code.