Android Single Shot Events com Flow

João Victor
Android Dev BR
Published in
4 min readJun 22, 2021

Muitos desenvolvedores Android se não passaram pelo problema de ações em eventos, provavelmente ainda vão passar.

Antes de começar, gostaria de avisar que falaremos um pouco sobre alguns padrões como Observable , Channels e Flow. Também será levantado o padrão de arquitetura MVVM e MVI.

Usarei bibliotecas do Kotlin como exemplo para nos ajudar com a resolução do problema, então, caso ainda não conheça alguma, recomendo que passe por elas antes de continuar com a leitura.

LiveData(Observable), Channels e Flow.

Nosso Problema

Bom, acredito que muitos projetos atualmente seguem o padrão de arquitetura MVVM no Android por ser muito indicado pela Google, além da mesma nos disponibilizar diversas bibliotecas que nos auxiliam na construção de uma arquitetura limpa.

Falando por alto, na arquitetura MVVM, o nosso ViewModel expõe instâncias LiveData observáveis, ou seja, sempre que alterado o valor da instância, os observáveis receberão essa atualização. O observador está diretamente ligado ao ciclo de vida do Android, fazendo com que essa classe seja LifeCycleAware.

Atenção nesse ponto. O LiveData sempre mantém o último valor enviado em seu buffer.

Vamos ver esse ViewModel como exemplo que ao iniciar, um UseCase que retorna um valor é invocado.

A partir do valor retornado, existem dois eventos. O primeiro, onde o valor não é nulo, será um evento para abrir outro Fragment. Caso contrario, será apresentado um evento para abrir modal de erro genérico.

Seguindo para o Fragment onde será observado os valores:

Aqui entra um ponto onde já mencionamos, como sabemos, o LiveData sempre mantem no buffer a ultima atualização. Então, sempre que recriar o Fragment, ele irá observar esse último valor e entramos em um loop infinito.

Bom, agora que temos o problema, precisamos da solução. Atualmente temos uma ótima solução, que seria o SingleLiveEvent, uma classe abstrata de LiveData que manipula os valores enviados para gente.

Essa solução resolve muito bem nosso problema, e aposto que você está se perguntando, então porquê estamos aqui?

Cada dia que passa, Kotlin Multiplataforma vem sendo mais utilizado, por trazer ótimos benefícios, sendo um deles a reutilização de código.

Como sabemos, LiveData está diretamente ligado ao ciclo de vida do Android. Sendo assim, nosso ViewModel acaba sendo único para uma plataforma.

E aí que está o ponto: Como podemos reutilizar essa solução de Single Shot Events?

Channels

Para criar uma instância de Channel, precisamos de um argumento chamado buffer, onde, a partir dele, é criado uma abstração diferente de Channel.

Hoje iremos falar somente do LinkedListChannel, onde precisamos passar como argumento Channel.UNLIMITE. Com LinkedListChannel, ao emitir um novo valor, a corotina não é suspensa. Esse comportamento é o que nós precisamos.

Channel é muito similar ao LiveData, onde podemos enviar e receber um valor, uma diferença crucial é que, a partir do momento que um valor foi coletado, o mesmo é removido do Buffer.

É ai que está a cereja do bolo! Como não queremos receber o mesmo valor repetidamente, ele nos atende perfeitamente, exatamente como a arquitetura com eventos MVI!

Conheça o novo SingleEvent :D

A partir dessa ideia, podemos criar uma abstração seguindo o padrão de imutabilidade, similar o MutableLiveData do Android.

Vamos separar responsabilidades por interfaces, referente ao emissor e coletor.

Interface Mutável:

Interface Imutável:

Abstração:

Repare que no método value() mapeamos nosso canal para Flow. Isso significa que nosso flow será um fluxo quente, ou seja, o flow não dependerá do collect para que seja iniciado o fluxo, pois o canal existe independente do coletor, diferente do fluxo frio. O flow coletor não será cancelado até que o canal principal for fechado, então tome cuidado com esse chamada.

Assim podemos expor a instância imutável para o Fragment e permanecer com a referência mutável no ViewModel.

Pronto! agora podemos coletar nossos eventos, que serão recebidos uma única vez em nosso Fragment.

Com isso, podemos reaproveitar nosso ViewModel e desfrutar de todas funcionalidade da biblioteca do Flow.

Com Flow, passamos a ter algumas outras opções antes de coletar, como por exemplo filter, map, combine e várias outras.

Veja um exemplo onde filtramos nosso flow para que colete somente um tipo de evento:

Testes Unitários

Nossa missão é garantir que nosso evento foi enviado uma única vez e, com isso, criamos uma extensão para validar rapidamente ou retornar null.

Vamos ver o exemplo utilizando MockK para nos auxiliar com mocks:

Uma extensão de auxilio foi criada para que tente coletar o flow por 10ms e finalizar o fluxo original, porque o mesmo é um canal quente.

Bônus

Pensando no Android, uma parte muito importante da plataforma seria o LifeCycle, e nossa classe não é LifeCycleAware. Vamos então utilizar uma extensão para que não tenhamos que nos preocupar com dados coletados fora do fluxo correto.

Agora podemos coletar um novo Flow onde as emissões originais serão canceladas assim que o ciclo de vida do Fragment entrar em um fluxo não desejado e tornará a receber sempre que iniciar o estado desejado.

Deixarei aqui alguns links de artigos referente a padrões e arquiteturas mencionados

MVVM: https://medium.com/android-dev-br/arquiteturas-em-android-mvvm-kotlin-android-architecture-components-databinding-lifecycle-d5e7a9023cf3

Imutabilidade: https://beginnersbook.com/2017/12/kotlin-variables-data-types/#:~:text=There%20are%20two%20types%20of,mutable%20variable%20can%20be%20changed.

MVI:https://www.raywenderlich.com/817602-mvi-architecture-for-android-tutorial-getting-started

Referências de artigos de bibliotecas utilizadas para realizar os Testes Unitários

MockK: https://medium.com/android-dev-br/o-guia-definitivo-sobre-dubl%C3%AAs-de-teste-no-android-parte-2-pr%C3%A1tica-233bf4f284f

--

--