A fondo con Flows & Channels — Parte 4: BroadcastChannels

Julián Ezequiel Picó
Droid LATAM
Published in
4 min readJul 6, 2020

English version

El texto está basado en la versión 1.3.7 de la biblioteca kotlinx.coroutines

Bienvenidos de nuevo! Si tengo suerte, ya estuvieron leyendo los artículos previos (en especial el relacionado a Channels) y están preparados para leer un poco más sobre las APIs de Stream en Kotlin. Hoy vamos a hablar sobre BroadcastChannels.

Pueden ver algunos ejemplos en el GitHub Repository. Hay varias implementaciones de casos de uso y ejemplos simples que te van a enseñar, en este caso, a usar BroadcastChannels. Sin embargo, como mencioné en artículos previos, hay ejemplos de Flows, Channels, StateFlows y hasta una app de ejemplo que utiliza todos los últimos.

Recuerden que este artículo pertenece a una serie de los mismos:

Este artículo va a ser más corto que el anterior, porque muchas de las cosas que deberíamos ver ya fueron discutidas en el artículo de Channels. Si te sentís un poco perdido, anda y revisalo!

Empecemos con la parte 4: Broadcast Channels!

Broadcast Channels

Entonces, ya sabemos que un Channel tiene un emisor y un receptor. También sabemos que los Channels tienen un mecanismo de transmisión unicast.

Pero, qué pasa si queremos tener más de un receptor? Bueno, para esto tenemos un componente diferente: el BroadcastChannel.

El BroadcastChannel es una interfaz que implementa únicamente a SendChannel, por lo que básicamente representa al emisor de un Channel.

Pero esperen!, si solo implementa la interfaz del emisor, cómo hacemos para recibir los valores del buffer? Tal vez viendo la interfaz puedas darte cuenta cómo.

Cuando queremos realizar una suscripción, llamamos al método openSubscription(). Allí, el BroadcastChannel crea un nuevo ReceiveChannel. Cuando un valor es enviado al buffer, es enviado a todas las suscripciones abiertas, en otras palabras, a todos los ReceiveChannels. Entonces, acá si tenemos un multicast real!

Un diagrama que puede explicar a alto nivel qué es un BroadcastChannel es el siguiente:

Podemos ver que:

  • Existe un Sender, que pone información dentro de un Buffer. Esto se realiza a través de operaciones con el SendChannel.
  • Existen uno o más Receivers, que obtienen información del Buffer. Esto se realiza a través de operaciones con cada ReceiveChannel.
  • Existe un Buffer, que nos ayuda a sincronizar al Sender y los Receivers.

Podemos decir que el diagrama que está arriba representa un BroadcastChannel y su comportamiento.

Creando BroadcastChannels

Todos los BroadcastChannels son creados, como los Channels, con una factory function

Como vimos con Channels, hay distintos tipos de BroadcastChannels.

No voy a profundizar en ellos, porque son equivalentes a los tipos de Channels. Sin embargo, les recomiendo que busquen información sobre el ConflatedBroadcastChannel, ya que creo que es el más utilizado en los artículos que puedan encontrar. Por qué no voy a hablar del mismo? Simple: hay otro stream, StateFlow, que lo va a reemplazar. Esperen al próximo artículo para saber un poco más!

Sobre el ConflatedBroadcastChannel, acá y acá pueden encontrar información interesante

Lo que sí es importante notar acá es que solo podemos crear BroadcastChannels de tipo Conflated, Buffered y Custom.

Emitiendo información

Como los BroadcastChannels implementan la interfaz SendChannel, usamos los mismos métodos que usábamos con Channels: offer() y send().

Si querés refrescar la memoria, revisa la sección “Emitiendo información — To offer() or to send() ?”.

Consumiendo valores

Las cosas son distintas acá! Los Channels implementan la interfaz ReceiveChannel, pero los BroadcastChannels no lo hacen. Entonces, cómo consumimos la información del stream?

Hay dos formas:

Utilizando openSubscription()

Ya vimos este método antes. Cuando lo llamamos, recibimos una instancia de ReceiveChannel. Luego, podemos consumir los valores tal como lo hacíamos con Channels.

Si querés refrescar la memoria, revisa la sección “Consumiendo valores”.

Cuando llamamos a este método, el BroadcastChannel crea un nuevo receptor y lo agrega a su “lista de suscriptores”.

Consumiendo con asFlow()

Tenemos una extension function que wrappea las emisiones del BroadcastChannel en un Flow, asFlow().

Es el equivalente a la función consumeAsFlow() que usábamos en Channels! Y eso significa que, de nuevo, podemos usar todo el poder de Flow para manejar los consumos.

Una cosa interesante que encontré es que, detrás de escena, Kotlin crea un ReceiveChannel y lo wrappea en una función emitAll():

Un poco más sobre consumo de información

Existen otros métodos que podemos utilizar, pero están deprecados o van a estarlo pronto: consumes(), consumesAll(), consume(), consumeEach().

Cancelando BroadcastChannels

Por último, vamos a hablar sobre cómo cancelar un BroadcastChannel.

Vimos que los mismos solo implementan la interfaz SendChannel, por lo que solo podemos cerrar un Channel, pero qué pasa con la cancelación? Esto es un comportamiento de los ReceiveChannels.

Afortunadamente, si volvemos a la definición de la interfaz, tenemos un método cancel(), y ese es el que vamos a usar para realizar una cancelación.

Este método ejecuta la cancelación de todos los suscriptores (todos los ReceiveChannel). Por cada uno llama a ReceiveChannel.cancel(), y esa llamada dispara todo lo que mencionamos en el artículo anterior.

Si querés refrescar la memoria, revisá la sección “Cerrando o Cancelando?”.

Hasta acá llegamos!

Ahora, ya tenemos una implementación real de un hot stream, y espero que puedan entender las diferencias existentes entre los distintos tipos de Kotlin Streams. Como vengo diciendo, hay muchos artículos por ahí que hablan sobre este tema, por lo que pueden seguir explorando para tener más conocimientos!

En el próximo artículo (el último!… por ahora), vamos a hablar de StateFlows.

Nos leemos! Y, si te gustó, podes compartirlo!

Agradecimientos al equipo Android de MediaMonks por el feedback.

--

--