# Turning Any Android Callback into a Flow with callbackFlow

DevFeed: [Turning Any Android Callback into a Flow with callbackFlow](<https://devfeed.tech/articles/turning-any-android-callback-into-a-flow-with-callbackflow-25119.md>)

Original publisher: [Read original article](<http://michaelevans.org/blog/2025/03/22/turning-any-android-callback-into-a-flow-with-callbackflow/>)

Author: Michael Evans

Published: 2025-03-23T03:10:16Z

Content type: tutorial

Language: en

Sources: [Gadget Habit](<https://devfeed.tech/sources/gadget-habit.md>)

Topics: [Android](<https://devfeed.tech/topics/android.md>), [Kotlin](<https://devfeed.tech/topics/kotlin.md>), [Coroutines](<https://devfeed.tech/topics/coroutines.md>), [Firebase](<https://devfeed.tech/topics/firebase.md>), [reactive](<https://devfeed.tech/topics/reactive.md>), [API](<https://devfeed.tech/topics/api.md>), [Jetpack Compose](<https://devfeed.tech/topics/jetpack-compose.md>)

Tags: [android](<https://devfeed.tech/tags/android.md>), [api](<https://devfeed.tech/tags/api.md>), [coroutines](<https://devfeed.tech/tags/coroutines.md>), [firebase](<https://devfeed.tech/tags/firebase.md>), [jetpack-compose](<https://devfeed.tech/tags/jetpack-compose.md>), [kotlin](<https://devfeed.tech/tags/kotlin.md>), [reactive](<https://devfeed.tech/tags/reactive.md>), [realtime-database](<https://devfeed.tech/tags/realtime-database.md>)

## AI overview

This tutorial explains how to use Kotlin's callbackFlow to adapt callback- or listener-based Android APIs into reactive Flow streams. Using Firebase Realtime Database as an example, it shows how to emit updates, propagate cancellation errors, and remove listeners when collection ends.

## Source excerpt

If you've been working with Android for any amount of time, you've probably run into APIs that expose their results using callbacks or listeners. Whether it's something like LocationManager, a custom SDK, or a third-party service like Firebase, you're often stuck adapting old-school async patterns into your modern reactive code. This approach is especially helpful in apps using Jetpack Compose, coroutines, or unidirectional data flow. Fortunately, Kotlin's callbackFlow makes this much easier. In this post, we'll show how to wrap a listener-based API using callbackFlow, so you can collect updates as a Flow. We'll use the Firebase Realtime Database as an example, but this pattern works for nearly anything. The Problem: Listeners Aren't Reactive Here's the classic way of listening to changes in the Firebase Realtime Database: 1 2 3 4 5 6 7 8 9 10 11 val postListener = object : ValueEventListener { override fun onDataChange(dataSnapshot: DataSnapshot) { val post = dataSnapshot.getValue<Post>() // update UI } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "loadPost:onCancelled", error.toException()) } } postReference.addValueEventListener(postListener) This works fine -- but it's imperative and not easily composable with things like StateFlow, LiveData, or Jetpack Compose. Let's fix that. The Fix: Wrap It with callbackFlow Kotlin's callbackFlow is designed for exactly this kind of situation -- where you need to bridge a listener-based API into a reactive stream. Here's what it looks like for Firebase: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fun Query.asFlow(): Flow<DataSnapshot> = callbackFlow { val listener = object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { trySend(snapshot).isSuccess // emit each snapshot into the Flow } override fun onCancelled(error: DatabaseError) { close(error.toException()) // cancel the flow on error } } // Start listening for updates addValueEventListener(listener) // Suspend until the flow is closed