Flow Timeout
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.