O artigo discutirá
UndeliverableException
no
RxJava2
versão
2.0.6
e posterior. Se alguém colidiu e não conseguiu descobrir ou não ouviu nada sobre esse problema, peço um gato. Eles solicitaram a tradução de problemas na
production
após a transição de
RxJava1
para
RxJava2
. O original foi escrito em 28 de dezembro de 2017, mas é melhor descobrir tarde do que nunca.
Todos somos bons desenvolvedores e detectamos erros no
onError
quando usamos o
RxJava
. Isso significa que nos protegemos de falhas de aplicativos, certo?
Não, não é verdade.Abaixo, veremos alguns exemplos nos quais o aplicativo
RxJava
devido ao
RxJava
, mesmo que o
onError
implementado corretamente.
Manipulador de erro básico no RxJava
RxJava
usa
RxJavaPlugins.onError
como o manipulador de erros base. Ele lida com todos os erros que não podem ser entregues ao assinante. Por padrão, todos os erros são enviados a ele, para que falhas críticas do aplicativo possam ocorrer.
2.0.6
descrevem este comportamento:
Um dos objetivos do design 2.x é a falta de erros perdidos. Às vezes, uma sequência termina ou é cancelada antes que a fonte aumente onError
. Nesse caso, o erro não tem para onde ir e é enviado para RxJavaPlugins.onError
Se o RxJava não tiver um manipulador de erros básico, esses erros serão ocultados para nós e os desenvolvedores ficarão no escuro sobre possíveis problemas no código.
A partir da versão
2.0.6
,
RxJavaPlugins.onError
tenta ser mais inteligente e compartilha erros de biblioteca / implementação e situações em que um erro não pode ser entregue. Os erros atribuídos à categoria de "bugs" são chamados como são, enquanto os demais são agrupados em
UndeliverableException
e depois chamados. Você pode ver toda essa lógica
aqui (
isBug
onError
e
isBug
).
Um dos principais erros dos novatos no
RxJava
OnErrorNotImplementedException
é o
OnErrorNotImplementedException
. Este erro ocorre se
observable
causar um erro e o método
onError
não estiver implementado no assinante. Este erro é um exemplo de erro que, para o manipulador de erros básico,
RxJava
é um "bug" e não se transforma em uma
UndeliverableException
.
UndeliverableException
Como os erros relacionados a "bugs" são fáceis de corrigir, não iremos nos deter neles. Os erros que o
RxJava
envolve em uma
UndeliverableException
são mais interessantes, pois nem sempre é óbvio o motivo pelo qual o erro não pode ser entregue ao
onError
.
Os casos em que isso pode acontecer dependem do que as fontes e assinantes fazem especificamente. Consideraremos exemplos abaixo, mas, em geral, podemos dizer que esse erro ocorre se não houver assinante ativo para quem o erro possa ser entregue.
Exemplo com zipWith ()
A primeira opção na qual você pode gerar uma
UndeliverableException
é a
zipWith
.
val observable1 = Observable.error<Int>(Exception()) val observable2 = Observable.error<Int>(Exception()) val zipper = BiFunction<Int, Int, String> { one, two -> "$one - $two" } observable1.zipWith(observable2, zipper) .subscribe( { System.out.println(it) }, { it.printStackTrace() } )
Combinamos duas fontes juntas, cada uma delas causando um erro. O que esperamos? Podemos supor que
onError
será chamado duas vezes, mas isso contradiz
a especificação de Reactive streams
.
Após uma única chamada para um evento do terminal ( onError
, onCompelete
), é necessário que não sejam feitas mais chamadas
Acontece que, com uma única chamada para
onError
segunda chamada não é mais possível. O que acontece quando ocorre um segundo erro na fonte? Ele será entregue ao
RxJavaPlugins.onError
.
Uma maneira fácil de entrar nessa situação é usar o
zip
para combinar chamadas de rede (por exemplo, duas chamadas de
Retrofit
retornando
Observable
). Se ocorrer um erro nas duas chamadas (por exemplo, não há conexão com a Internet), ambas as fontes causarão erros, a primeira delas cairá na implementação
onError
e a segunda será entregue ao manipulador de erros base (
RxJavaPlugins.onError
).
Exemplo de ConnectableObservable sem assinantes
ConnectableObservable
também pode gerar uma
UndeliverableException
. Vale lembrar que o
ConnectableObservable
gera eventos independentemente da presença de assinantes ativos, basta chamar o método
connect()
. Se ocorrer um erro no
ConnectableObservable
quando não houver assinantes, ele será entregue ao manipulador de erros base.
Aqui está um exemplo bastante inocente que pode causar esse erro:
someApi.retrofitCall()
Se
someApi.retrofitCall()
causar um erro (por exemplo, não há conexão com a Internet), o aplicativo
RxJava
, pois o erro de rede será entregue ao
RxJava
erros base do
RxJava
.
Este exemplo parece fictício, mas é muito fácil entrar em uma situação em que o
ConnectableObservable
ainda está conectado, mas não possui assinantes. Me deparei com isso ao usar o
autoConnect()
ao chamar uma API.
autoConnect()
não desativa automaticamente o
Observable
.
onStop
a inscrição no método
onStop
da
Activity
, mas o resultado da chamada de rede retornou após a destruição da
Activity
e o aplicativo
UndeliverableException
com
UndeliverableException
.
Tratamento de erros
Então, o que fazer com esses erros?
O primeiro passo é examinar os erros que ocorrem e tentar determinar o que os causa. Idealmente, se você puder corrigir o problema em sua origem para impedir que o erro seja
RxJavaPlugins.onError
ao
RxJavaPlugins.onError
.
A solução para o exemplo
zipWith
é pegar uma ou ambas as fontes e implementar
um dos métodos para detectar erros nelas. Por exemplo, você pode usar
onErrorReturn
para passar o valor padrão em vez de um erro.
O exemplo do
ConnectableObservable
é mais simples de corrigir - basta desconectar o
Observable
quando o último assinante cancelar a inscrição.
autoConnect()
, por exemplo, possui uma implementação sobrecarregada que
autoConnect()
uma função que captura o tempo de conexão (
mais pode ser visto aqui ).
Outra maneira de resolver o problema é substituir o manipulador de erros base pelo seu. O
RxJavaPlugins.setErrorHandler(Consumer<Throwable>)
o ajudará com isso. Se esta é a solução certa para você, você pode capturar todos os erros enviados para
RxJavaPlugins.onError
e manipulá-los como achar melhor. Essa solução pode ser bastante complicada - lembre-se de que o
RxJavaPlugins.onError
recebe erros de todos os fluxos do
RxJava
no aplicativo.
Se você criar manualmente seu
Observable
, poderá chamar
emitter.tryOnError()
vez de
emitter.tryOnError()
. Este método relata um erro apenas se o fluxo não for encerrado e tiver assinantes. Lembre-se de que este método é experimental.
A moral deste artigo é que você não pode ter certeza de que não há erros ao trabalhar com o RxJava se você simplesmente implementou o
onError
nos assinantes. Você deve estar ciente das situações em que os erros podem não estar disponíveis para os assinantes e garantir que essas situações sejam tratadas.