A powerful operator for real-time, cancellable transformations
Table of Contents
Kotlin’s Flow API gives us a rich set of operators for transforming asynchronous
streams of data. Among them, one of the most important but often
overlooked operators is transformLatest.
If you’ve ever worked with search boxes, live filters, continuously updating UI,
or streams where newer data should cancel older work, then transformLatest is
exactly the tool you were looking for.
In this post, we’ll explore:
- What transformLatest actually does
- Why it exists (and what problem it solves)
- How it differs from similar operators (map, flatMapLatest, transform)
- Practical use cases
- Examples you can use in real apps
What is transformLatest? #
transformLatest is a Flow operator that launches a block for each upstream
value, but cancels the previous block if a new value arrives.
In other words:
Only the transformation of the latest value continues; older ones are cancelled.
It is very similar to flatMapLatest, but gives you the freedom to emit
multiple
values or perform custom logic inside a suspending block.
Basic Example #
flowOf(1, 2, 3)
.transformLatest { value ->
emit("Start $value")
delay(200)
emit("End $value")
}
.collect { println(it) }
Output #
Start 1
Start 2
Start 3
End 3
What happened? #
- Start 1 is emitted
- Before it can finish, 2 arrives → previous block cancelled
- Same for 3, but since it’s the last one, it finishes and emits both Start and End
Why do we need transformLatest? #
Many real-world operations involve long-running work that must be cancelled when new data arrives:
- User types in a search box
- User selects a different item before the previous fetch finishes
- Filtering a list in real time
- Debounced or throttled operations
- Continuously updating UI while doing backend calls
In all such cases:
You don’t want stale results. You want the latest input to cancel previous work.
That’s exactly what transformLatest provides.
transformLatest vs Similar Operators #
map #
- Transforms values one-to-one
- Does not cancel previous transformations
transform #
- Can emit multiple values
- Does not cancel previous transformations
flatMapLatest #
- Cancels previous operations
- But returns a Flow → suitable for simple mapping to another Flow
- Less flexible compared to transformLatest
transformLatest #
- Cancel previous block ✔
- Emit multiple values ✔
- Custom logic ✔
- Arbitrary suspend calls inside ✔
This makes transformLatest the “escape hatch” for handling complex logic
with
cancellation semantics.
Real-World Use Case #
Search-as-you-type #
Without transformLatest, each query triggers a network call—even if the user
types fast.
queryFlow
.transformLatest { query ->
emit(UiState.Loading)
val results = api.search(query)
emit(UiState.Success(results))
}
.collect { render(it) }
If the user types: k, ko, kot, kotl, kotlin, only the last network call completes.
Stepwise UI Updates #
You can emit intermediate UI states:
idFlow.transformLatest { id ->
emit(UiState.Loading(id))
val details = repository.fetchDetails(id)
emit(UiState.ShowDetails(details))
val stats = repository.fetchStats(id)
emit(UiState.ShowStats(stats))
}
Switching to a new id cancels all previous pending requests immediately.
Animated UI or Progress Updates #
actionFlow.transformLatest {
emit("Starting...")
repeat(5) { i ->
delay(300)
emit("Progress $i")
}
emit("Done")
}
If another action arrives midway, ongoing progress is discarded.
Summary #
transformLatest is one of the most powerful Flow operators because it allows:
- Cancellable transformations
- Multiple emissions per input
- Custom suspendable work
- Real-time, responsive UI patterns
Think of it as:
transform+ automatic cancellation of previous work
Use it whenever you need to respond to rapidly changing input without letting older work finish unnecessarily.