Operadores de serviços públicos observáveis no RxJava
1. Visão geral
Neste artigo, descobriremos alguns operadores de utilitário para trabalhar comObservables em RxJava e como implementar os personalizados.
An operator is a function that takes and alters the behavior of an upstream Observable<T> and returns a downstream Observable<R> or Subscriber, onde os tipos T e R podem ou não ser iguais.
Os operadores agrupamObservables existentes e os aprimoram normalmente interceptando a assinatura. Isso pode parecer complicado, mas na verdade é bastante flexível e não é difícil de entender.
2. Do
Existem várias ações que podem alterar os eventos de ciclo de vida deObservable.
O operador doOnNext modifica a fonteObservable so that it invokes an action when the onNext is called .__
The doOnCompleted operator registers an action which is called if the resulting Observable terminates normally, chamando o métodoObserver deonCompleted:
Observable.range(1, 10)
.doOnNext(r -> receivedTotal += r)
.doOnCompleted(() -> result = "Completed")
.subscribe();
assertTrue(receivedTotal == 55);
assertTrue(result.equals("Completed"));
O operadordoOnEach modifica a fonteObservable para notificar umObserver para cada item e estabelecer um retorno de chamada que será chamado sempre que um item for emitido.
O operadordoOnSubscribe registra uma ação que é chamada sempre que umObserver assina oObservable resultante.
Há também odoOnUnsubscribe operator que faz o oposto dedoOnSubscribe:
Observable.range(1, 10)
.doOnEach(new Observer() {
@Override
public void onCompleted() {
System.out.println("Complete");
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(Integer value) {
receivedTotal += value;
}
})
.doOnSubscribe(() -> result = "Subscribed")
.subscribe();
assertTrue(receivedTotal == 55);
assertTrue(result.equals("Subscribed"));
Quando umObservable é concluído com um erro, podemos usar o operadordoOnError para executar uma ação.
DoOnTerminate operator registra uma ação que será invocada quando um Observable for concluído, com êxito ou com um erro:
thrown.expect(OnErrorNotImplementedException.class);
Observable.empty()
.single()
.doOnError(throwable -> { throw new RuntimeException("error");})
.doOnTerminate(() -> result += "doOnTerminate")
.doAfterTerminate(() -> result += "_doAfterTerminate")
.subscribe();
assertTrue(result.equals("doOnTerminate_doAfterTerminate"));
Também há umFinallyDo operator – which was deprecated in favor of doAfterTerminate. It registers an action when an Observable completes.
3. ObserveOn vsSubscribeOn
Por padrão, umObservable junto com a cadeia de operadores irá operar na mesma thread em que seu métodoSubscribe é chamado.
O operador[.operator]#ObserveOn #especifica umScheduler diferente queObservable usará para enviar notificações paraObservers:
Observable.range(1, 5)
.map(i -> i * 100)
.doOnNext(i -> {
emittedTotal += i;
System.out.println("Emitting " + i
+ " on thread " + Thread.currentThread().getName());
})
.observeOn(Schedulers.computation())
.map(i -> i * 10)
.subscribe(i -> {
receivedTotal += i;
System.out.println("Received " + i + " on thread "
+ Thread.currentThread().getName());
});
Thread.sleep(2000);
assertTrue(emittedTotal == 1500);
assertTrue(receivedTotal == 15000);
Vemos que os elementos foram produzidos emmain threade foram empurrados até a primeira chamadamap.
Mas depois disso, oobserveOn redirecionou o processamento para umcomputation thread, que foi usado ao processarmape oSubscriber. final
One problem that may arise with observeOn is the bottom stream can produce emissions faster than the top stream can process them. Isso pode causar problemas combackpressure que podemos ter que considerar.
Para especificar em qualScheduler oObservable deve operar, podemos usar o operadorsubscribeOn:
Observable.range(1, 5)
.map(i -> i * 100)
.doOnNext(i -> {
emittedTotal += i;
System.out.println("Emitting " + i
+ " on thread " + Thread.currentThread().getName());
})
.subscribeOn(Schedulers.computation())
.map(i -> i * 10)
.subscribe(i -> {
receivedTotal += i;
System.out.println("Received " + i + " on thread "
+ Thread.currentThread().getName());
});
Thread.sleep(2000);
assertTrue(emittedTotal == 1500);
assertTrue(receivedTotal == 15000);
SubscribeOn instrui a fonteObservable qual thread usar para emitir itens - somente esta thread irá enviar itens paraSubscriber. Ele pode ser colocado em qualquer lugar do fluxo, pois afeta apenas a assinatura.
Efetivamente, podemos usar apenas umsubscribeOn, mas podemos ter qualquer número de operadoresobserveOn. Podemos mudar as emissões de um segmento para outro com facilidade usandoobserveOn.
4. Single eSingleOrDefault
O operadorSingle retorna umObservable que emite o único item emitido pela fonteObservable:
Observable.range(1, 1)
.single()
.subscribe(i -> receivedTotal += i);
assertTrue(receivedTotal == 1);
Se a fonteObservable produzir zero ou mais de um elemento, uma exceção será lançada:
Observable.empty()
.single()
.onErrorReturn(e -> receivedTotal += 10)
.subscribe();
assertTrue(receivedTotal == 10);
Por outro lado, o operadorSingleOrDefault é muito semelhante aSingle,, o que significa que também retorna umObservable que emite o único item da fonte, mas, além disso, podemos especificar um valor padrão :
Observable.empty()
.singleOrDefault("Default")
.subscribe(i -> result +=i);
assertTrue(result.equals("Default"));
Mas se a fonteObservable emitir mais de um item, ela ainda emitirá umIllegalArgumentExeption:
Observable.range(1, 3)
.singleOrDefault(5)
.onErrorReturn(e -> receivedTotal += 10)
.subscribe();
assertTrue(receivedTotal == 10);
S conclusão simples:
-
Se for esperado que a fonteObservable possa ter nenhum ou um elemento, entãoSingleOrDefault deve ser usado
-
Se estivermos lidando com potencialmente mais de um item emitido em nosso`Observable`e quisermos apenas emitir o primeiro ou o último valor, podemos usar outros operadores comofirst oulast
5. Timestamp
The Timestamp operator attaches a timestamp to each item emitted by the source Observable antes de reemitir aquele item em sua própria sequência. O registro de data e hora indica em que horário o item foi emitido:
Observable.range(1, 10)
.timestamp()
.map(o -> result = o.getClass().toString() )
.last()
.subscribe();
assertTrue(result.equals("class rx.schedulers.Timestamped"));
6. Delay
Este operador modifica sua fonteObservable pausando por um determinado incremento de tempo antes de emitir cada um dos itens de fonteObservable’s.
Ele compensa a sequência inteira usando o valor fornecido:
Observable source = Observable.interval(1, TimeUnit.SECONDS)
.take(5)
.timestamp();
Observable delayedObservable
= source.delay(2, TimeUnit.SECONDS);
source.subscribe(
value -> System.out.println("source :" + value),
t -> System.out.println("source error"),
() -> System.out.println("source completed"));
delayedObservable.subscribe(
value -> System.out.println("delay : " + value),
t -> System.out.println("delay error"),
() -> System.out.println("delay completed"));
Thread.sleep(8000);
Existe um operador alternativo, com o qual podemos atrasar a assinatura da fonte Observable chamadodelaySubscription.
O operadorDelay é executado emcomputationScheduler por padrão, mas podemos escolher umScheduler diferente passando-o como um terceiro parâmetro opcional paradelaySubscription.
7. Repeat
Repeat simplesmente intercepta a notificação de conclusão do upstream e, em vez de transmiti-la, ele se inscreve novamente.
Portanto, não é garantido querepeat continuará percorrendo a mesma sequência de eventos, mas acontece que é o caso quando o fluxo ascendente é um fluxo fixo:
Observable.range(1, 3)
.repeat(3)
.subscribe(i -> receivedTotal += i);
assertTrue(receivedTotal == 18);
8. Cache
O operadorcache fica entresubscribe e nossoObservable personalizado.
Quando o primeiro assinante aparece,cache delega a assinatura para oObservable subjacente e encaminha todas as notificações (eventos, conclusões ou erros) downstream.
No entanto, ao mesmo tempo, mantém uma cópia de todas as notificações internamente. Quando um assinante subsequente deseja receber notificações push,cache não delega mais para oObservable subjacente, mas, em vez disso, alimenta os valores em cache:
Observable source =
Observable.create(subscriber -> {
System.out.println("Create");
subscriber.onNext(receivedTotal += 5);
subscriber.onCompleted();
}).cache();
source.subscribe(i -> {
System.out.println("element 1");
receivedTotal += 1;
});
source.subscribe(i -> {
System.out.println("element 2");
receivedTotal += 2;
});
assertTrue(receivedTotal == 8);
9. Using
Quando umobserver assina oObservable retornado deusing(), ele usará a função de fábricaObservable para criar oObservable oobserver irá ... observar, enquanto ao mesmo tempo usa a função de fábrica de recursos para criar qualquer recurso que tenhamos projetado para fazer.
Quandoobserver cancela a inscrição deObservable, ou quandoObservable termina,using chama a terceira função para descartar o recurso criado:
Observable values = Observable.using(
() -> "resource",
r -> {
return Observable.create(o -> {
for (Character c : r.toCharArray()) {
o.onNext(c);
}
o.onCompleted();
});
},
r -> System.out.println("Disposed: " + r)
);
values.subscribe(
v -> result += v,
e -> result += e
);
assertTrue(result.equals("resource"));
10. Conclusão
Neste artigo, falamos sobre como usar os operadores de utilitários RxJava e também como explorar seus recursos mais importantes.
O verdadeiro poder do RxJava reside em seus operadores. As transformações declarativas dos fluxos de dados são seguras, porém expressivas e flexíveis.
Com uma base sólida em programação funcional, os operadores desempenham um papel decisivo na adoção do RxJava. O domínio dos operadores internos é a chave para o sucesso nesta biblioteca.
O código-fonte completo do projeto, incluindo todos os exemplos de código usados aqui, pode ser encontradoover on GitHub.