Convertir cualquier callback en un Flow con CallbackFlow
Antonio Leiva

Existen varios tipos de Flows muy particulares que nos van a solucionar la vida cuando tengamos que hacer cosas muy concretas.

Ya vimos StateFlow en un artículo anterior, y en esta ocasión hablamos de CallbackFlow

¿Qué es CallbackFlow?

Es un tipo de Flow que nos permite convertir cualquier API basada en callbacks (o en Listeners, que en Android pasa mucho) en un Flow que podemos recolectar, transformar y usar como hemos visto en el resto de artículos sobre este tema.

Esto va a hacer que podamos convertir cualquier API al mundo reactivo.

En Android, por ejemplo, podríamos convertir cualquier Listener de los componentes de la interfaz de usuario en un Flow, de la misma forma que por ejemplo la librería RxBinding lo hace con RxJava.

Vamos a hacer algunos ejemplos:

Convirtiendo setOnClickListener a un Flow

Este es el ejemplo más clásico. Tenemos el típico setOnClickListener, y queremos emitir eventos cada vez que se haga click en el botón.

Lo primero sería crearnos una función de extensión que nos devuelva ese Flow

val View.onClickEvents: Flow<View>
    get() = ...

Y aquí es donde creamos nuestro CallbackFlow. se usa una función para ello, un bloque de código al estilo de flow { }:

val View.onClickEvents: Flow<View>
    get() = callbackFlow {
        ...
    }

Después vamos a crearnos un listener que extienda de View.OnClickListener y asignárselo a la vista:

val View.onClickEvents: Flow<View>
    get() = callbackFlow {
        val onClickListener = View.OnClickListener { ... }
        setOnClickListener(onClickListener)
    }

En el listener, lo que haremos será llamar a la función offer(), que es la que ofrece un nuevo valor al Flow para emitirlo:

val View.onClickEvents: Flow<View>
    get() = callbackFlow {
        val onClickListener = View.OnClickListener { offer(it) }
        setOnClickListener(onClickListener)
    }

Casi para terminar, le vamos a decir que cuando el Flow se cierre, nos desuscribamos del listener. Para ello::

val View.onClickEvents: Flow<View>
    get() = callbackFlow {
        val onClickListener = View.OnClickListener { offer(it) }
        setOnClickListener(onClickListener)
        awaitClose { setOnClickListener(null) }
    }.

Para terminar, lo ideal en las interfaces de usuario, en las que solo nos interesa el valor final, y no toda la lista de valores intermedios, es que usemos la función conflate().

Con esto, si el recolector tarda más de lo esperado en ejecutar la operación con el resultado, esto no va a suspender al Flow hasta que acabe. El Flow seguirá emitiendo valores, y cuando el recolector acabe con el que estaba, cogerá el último emitido en ese momento.

val View.onClickEvents: Flow<View>
    get() = callbackFlow {
        val onClickListener = View.OnClickListener { offer(it) }
        setOnClickListener(onClickListener)
        awaitClose { setOnClickListener(null) }
    }.conflate()

Convirtiendo el Scroll Listener a un Flow

En artículos anteriores, nos suscribimos al evento de scroll del RecyclerView de la siguiente forma:

val layoutManager = recycler.layoutManager as GridLayoutManager

recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        viewModel.lastVisible.value = layoutManager.findLastVisibleItemPosition()
    }
})

Con ellos, podíamos actualizar el StateFlow basados en la última posición visible del LayoutManager. ¿Por qué no acortamos todo esto, y nos creamos un Flow que devuelva las actualizaciones de la última posición visible?

La estructura sería esta:

val RecyclerView.lastVisibleEvents: Flow<Int>
    get() = callbackFlow<Int> {
        ...
    }.conflate()

Vamos a recuperar el LayoutManager y crear el OnScrollListener:

val RecyclerView.lastVisibleEvents: Flow<Int>
    get() = callbackFlow<Int> {
        val lm = layoutManager as GridLayoutManager

        val listener = object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                ...
            }
        }
        addOnScrollListener(listener)
    }.conflate()

Ya solo nos quedaría ofrecer la última posición visible:

val RecyclerView.lastVisibleEvents: Flow<Int>
    get() = callbackFlow<Int> {
        val lm = layoutManager as GridLayoutManager

        val listener = object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                offer(lm.findLastVisibleItemPosition())
            }
        }
        awaitClose { removeOnScrollListener(listener) }
    }.conflate()

Y por supuesto, desuscribirnos del Listener cuando se cierre el Flow:

val RecyclerView.lastVisibleEvents: Flow<Int>
    get() = callbackFlow<Int> {
        val lm = layoutManager as GridLayoutManager

        val listener = object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                offer(lm.findLastVisibleItemPosition())
            }
        }
        addOnScrollListener(listener)
        awaitClose { removeOnScrollListener(listener) }
    }.conflate()

Conclusión

Ya ves que es muy sencillo adaptar cualquier cosa a un Flow.

Lo bueno es que a partir de ese punto, puedes combinarlos con otros, realizar transformaciones sobre ellos y realizar todo tipo de operaciones para que se adapten a tus necesidades.

Los Flows son una herramienta muy potente que nos mueve de forma muy sencilla la mundo de la programación reactiva en Kotlin.

Quizá también te interese…

¿Qué es Kotlin Multiplataforma?

¿Qué es Kotlin Multiplataforma?

En el mundo actual, donde los dispositivos móviles están presentes en nuestra vida diaria, es fundamental para los desarrolladores crear aplicaciones que se adapten a diferentes sistemas operativos. Kotlin Multiplataforma es una herramienta que facilita la creación de...

5 trucos de Kotlin para escribir código más eficiente en Android

5 trucos de Kotlin para escribir código más eficiente en Android

El lenguaje de programación Kotlin se ha convertido en el más popular para el desarrollo de aplicaciones de Android en los últimos años. Su sintaxis concisa y moderna, junto con su capacidad para mejorar la eficiencia de código, lo convierten en una opción atractiva...

Cómo crear un backend en Kotlin usando Ktor

Cómo crear un backend en Kotlin usando Ktor

Ktor es un framework de servidor web ligero y rápido para Kotlin, desarrollado por JetBrains. Es ideal para crear aplicaciones web y servicios RESTful, y es muy fácil de usar y configurar. En este artículo, vamos a ver cómo crear un backend para una aplicación de...

0 comentarios

Enviar un comentario

Los datos personales que proporciones a través de este formulario quedarán registrados en un fichero de DevExpert, S.L.U., con el fin de gestionar los comentarios que realizas en este blog. La legitimación se realiza a través del consentimiento de la parte interesada. Si no se acepta, no podrás comentar en este blog. Los datos que proporciona solo se utilizan para evitar el correo no deseado y no se usarán para nada más. Puede ejercer los derechos de acceso, rectificación, cancelación y oposición en contacto@devexperto.com.

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Acepto la política de privacidad *