Flow Timeout

Roque Buarque
Android Dev BR
Published in
4 min readFeb 22, 2021

Isso mesmo, aqui vamos falar sobre um operador que não existe no flow.

Requisitos

Para melhor entender esse artigo, se faz necessário estar familiarizado com:

  • Kotlin
  • Coroutines
  • Programação reativa / Observer pattern

Um pouco de contexto!

Todo meu background com programação reativa é com rxJava, porém há pouco mais de 6 meses surgiu a oportunidade para refatorar um app de serviço interno na empresa que eu trabalho.

Como a manutenção desse App é feita também por desenvolvedores que não são de mobile, e porque até então o App utilizava BroadcastReceivers + Intent em todo lugar, busquei uma solução para trazer o padrão observer com uma curva de aprendizado um pouco menor que a do rx, logo veio a ideia de usar Coroutines + Flow.

Cade o timeout???

A simplicidade do flow comparado com imensa variedade de operadores do rx me fez gostar de flow logo de primeira. Até o momento em que precisei implementar um timeout, confesso que fiquei muito decepcionado e quase arrependido por ter escolhido flow para aquele refactor.

E agora? Por onde começar?

Foi isso que me perguntei, depois de algumas pesquisas percebi o porque o flow não contém muitos operadores, mais precisamente, o porque o flow não tem o bendito do timeout. Aqui vai a resposta para a pergunta, flow cade o timeout? O timeout está no coroutines.

Após algumas pesquisas, tentativas e erros, cheguei na solução que vou apresentar pra você agora.

Ta aqui o timeout!

Como disse anteriormente, eu estava no meio de um refactor onde eu precisava tirar ou pelo menos minizar as dezenas de BroadcastReceivers e Intents e trazer um código elegante com cadeia de observables. Vamos para os exemplos de código então.

Gostaria de enfatizar duas funções que estou usando no código acima.

withContext: que basicamente realiza a chamada de um bloco suspenso específico dado um contexto de coroutines e suspende até completar, e retornar o resultado.
withTimeoutOrNull: Roda o bloco suspenso dentro de um coroutines com um timeout específico e retorna null caso esse timeout exceda o tempo.

Já que o coroutines faz tudo isso pra você, agora faz um pouco mais de sentido o flow não ter um operador de timeout, certo?

Resolvido a questão do timeout! Porém eu ainda preciso suspender o coroutines que foi aberto e a partir dessa suspenção conseguir dar o trigger do meu timeout, para isso utilizei a função suspendCancellableCoroutine como você pode ver abaixo.

No código acima estou suspendendo o coroutines, eu quero fazer isso até receber a resposta do meu callback.getIntent, caso ele demore para responder, automaticamente após o tempo em que setei no withTimeoutOrNull receberei null como retorno da função getResult pois o tempo se excedeu.

Por fim tudo implementado, meu flow vai enviar o resultado para quem estiver coletando suas emissões.

Um breve passo a posso do que esta acontecendo no código acima:

  • Tenho uma função com dois parâmetros que me retorna um Flow de uma intent nullable que vem de resposta do meu broadcast receiver.
  • Na linha 2 eu chamo a high-order function flow para criar um cold flow suspenso e conseguir emitir o resultado ou o null do timeout.
  • Linha 4 eu faço a requisição que não me devolve nada como resultado, apenas da o trigger para receber a Intent do broadcast-receiver após a resposta do servidor.
  • Linha 6 eu aguardo meu broadcast receiver responder ou meu outro contexto do coroutines dar timeout para emitir um novo valor.

Ligando os pontos

Uma breve explicação do código acima

  • Linha 1 tem a função someApiRequest que em sua implementação faz a requisição para meu sender/repositório e chama outra função para emitir algum valor.
  • Linha 10 tem a função getResult que em sua implementação abre outro coroutines com a função withContext passando o Dispatcher.IO. Também seta o timeout com a função withTimeoutOrNull, por fim começa a observar o callback.
  • Linha 18 suspende o coroutines aberto na função da linha 10 e aguarda o resultado

Bônus

Agora que ja vimos o código, gostaria de mostrar uma ilustração para melhor exemplificar o que e como esta acontecendo esse timeout por baixo dos panos.

Falamos bastante sobre coroutines, suspensão, escopo e contexto, então vamos tentar entender na ilustração abaixo como tudo isso esta conectado.

O passo a passo dessa ilustração seria o seguinte:

  • Criar o flow utilizando a função flow{ } que vai suspender o que estiver dentro do bloco.
  • Abrir outro coroutines com a função withContext para que eu consiga suspender e aguardar meu resultado ou dar um timeout.
  • Setar o timeout com a funçāo withTimeoutOrNull
  • Suspender com a função suspendCancellableCoroutine o escopo do coroutines que esta rodando em paralelo até ter o meu resultado.

Outras opções?

Essa é a pergunta que te faço no final desse artigo, você já implementou um timeout para o flow de alguma outra maneira? Me deixe saber nos comentários.

Notou que esse é o artigo número #1 desse tema? No próximo artigo vou mostrar como implementar exatamente a mesma réplica do rx timeout no flow, ficará dessa maneira:

Conclusão

Achei o flow muito mais simples de aprender comparado ao rx, você pode notar pela diferença de operadores disponíveis no flow em sua documentação comparado com a quantidade de operadores oferecido pelo rx.

Apesar de ter entendido o real motivo do flow não ter o timeout confesso que foi muito mais trabalhoso implementar na mão tendo em vista que estava acostumado com o rx já disponibilizar esse operador.

Se você leu até aqui e gostou, não esqueça de deixar alguns claps para incentivar quem está produzindo conteúdos, me siga para não perder os próximos artigos, um abraço e até a próxima.

--

--