Filtrando Observáveis ​​no RxJava

Filtrando Observáveis ​​no RxJava

1. Introdução

Seguindo oIntroduction to RxJava, vamos olhar para os operadores de filtragem.

Em particular, vamos nos concentrar em filtragem, pulo, filtragem de tempo e algumas operações de filtragem mais avançadas.

2. Filtragem

Ao trabalhar comObservable, às vezes é útil selecionar apenas um subconjunto de itens emitidos. Para isso,RxJava offers various filtering capabilities.

Vamos começar olhando para o métodofilter.

2.1. O operadorfilter

Simply put, the filter operator filters an Observable making sure that emitted items match specified condition, que vem na forma dePredicate.

Vamos ver como podemos filtrar apenas os valores ímpares daqueles emitidos:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable
  .filter(i -> i % 2 != 0);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(1, 3, 5, 7, 9);

2.2. O operadortake

Ao filtrar comtake, a lógica resulta na emissão dos primeiros itensn enquanto ignora os itens restantes.

Vamos ver como podemos filtrar osourceObservablee emitir apenas os dois primeiros itens:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.take(3);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(1, 2, 3);

2.3. O operadortakeWhile

Ao usartakeWhile,, oObservable filtrado continuará emitindo itens até encontrar um primeiro elemento que não corresponda aoPredicate.

Vamos ver como podemos usartakeWhile - com uma filtragemPredicate:

Observable sourceObservable = Observable.just(1, 2, 3, 4, 3, 2, 1);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable
  .takeWhile(i -> i < 4);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(1, 2, 3);

2.4. O operadortakeFirst

Sempre que quisermos emitir apenas o primeiro item que corresponda a uma determinada condição, podemos usartakeFirst().

Vamos dar uma olhada rápida em como podemos emitir o primeiro item maior que 5:

Observable sourceObservable = Observable
  .just(1, 2, 3, 4, 5, 7, 6);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable
  .takeFirst(x -> x > 5);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(7);

2.5. Operadores defirst efirstOrDefault

Um comportamento semelhante pode ser alcançado usando a APIfirst:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.first();

filteredObservable.subscribe(subscriber);

subscriber.assertValue(1);

No entanto, caso desejemos especificar um valor padrão, se nenhum item for emitido, podemos usarfirstOrDefault:

Observable sourceObservable = Observable.empty();

Observable filteredObservable = sourceObservable.firstOrDefault(-1);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(-1);

2.6. O operadortakeLast

Em seguida, se quisermos emitir apenas os últimos itensn emitidos por umObservable, podemos usartakeLast.

Vamos ver como é possível emitir apenas os três últimos itens:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.takeLast(3);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(8, 9, 10);

Devemos lembrar que isso retarda a emissão de qualquer item da fonteObservable até que seja concluída.

2.7. last elastOrDefault

Se quisermos emitir apenas o último elemento, além de usartakeLast(1), podemos usarlast.

Isso filtraObservable, emitindo apenas o último elemento, que opcionalmente verifica uma filtragemPredicate:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable
  .last(i -> i % 2 != 0);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(9);

Caso oObservable esteja vazio, podemos usarlastOrDefault, que filtra oObservable emitindo o valor padrão.

O valor padrão também é emitido se o operadorlastOrDefault for usado e não houver nenhum item que verifique a condição de filtragem:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable =
  sourceObservable.lastOrDefault(-1, i -> i > 10);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(-1);

2.8. Operadores deelementAt eelementAtOrDefault

Com o operadorelementAt, podemos escolher um único item emitido pela fonteObservable, especificando seu índice:

Observable sourceObservable = Observable
  .just(1, 2, 3, 5, 7, 11);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.elementAt(4);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(7);

No entanto,elementAt lançará umIndexOutOfBoundException se o índice especificado exceder o número de itens emitidos.

Para evitar essa situação, é possível usarelementAtOrDefault – que retornará um valor padrão caso o índice esteja fora do intervalo:

Observable sourceObservable = Observable
  .just(1, 2, 3, 5, 7, 11);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable
 = sourceObservable.elementAtOrDefault(7, -1);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(-1);

2.9. O operadorofType

Sempre queObservable emite itensObject, é possível filtrá-los com base em seu tipo.

Vamos ver como podemos filtrar apenas os itens do tipoString emitidos:

Observable sourceObservable = Observable.just(1, "two", 3, "five", 7, 11);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.ofType(String.class);

filteredObservable.subscribe(subscriber);

subscriber.assertValues("two", "five");

3. Pulando

Por outro lado, quando queremos filtrar ou pular alguns dos itens emitidos por umObservable,RxJava offers a few operators as a counterpart of the filtering ones, que discutimos anteriormente.

Vamos começar olhando para o operadorskip, a contraparte detake.

3.1. O operadorskip

Quando umObservable emite uma sequência de itens, é possível filtrar ou pular alguns dos primeiros itens emitidos usandoskip.

Por exemplo. vamos ver como é possível pular os primeiros quatro elementos:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.skip(4);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(5, 6, 7, 8, 9, 10);

3.2. O operadorskipWhile

Sempre que quisermos filtrar todos os primeiros valores emitidos por umObservable que falham em um predicado de filtragem, podemos usar o operadorskipWhile:

Observable sourceObservable = Observable
  .just(1, 2, 3, 4, 5, 4, 3, 2, 1);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable
  .skipWhile(i -> i < 4);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(4, 5, 4, 3, 2, 1);

3.3. TheskipLast Operador

O operadorskipLast nos permite pular os itens finais emitidos porObservable, aceitando apenas aqueles emitidos antes deles.

Com isso, podemos, por exemplo, pular os últimos cinco itens:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.skipLast(5);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(1, 2, 3, 4, 5);

3.4. Operadores dedistinct edistinctUntilChanged

O operadordistinct retorna umObservable que emite todos os itens emitidos porsourceObservable que são distintos:

Observable sourceObservable = Observable
  .just(1, 1, 2, 2, 1, 3, 3, 1);
TestSubscriber subscriber = new TestSubscriber();

Observable distinctObservable = sourceObservable.distinct();

distinctObservable.subscribe(subscriber);

subscriber.assertValues(1, 2, 3);

No entanto, se quisermos obter umObservable que emita todos os itens emitidos porsourceObservable que são distintos de seu predecessor imediato, podemos usar o operadordistinctUntilChanged:

Observable sourceObservable = Observable
  .just(1, 1, 2, 2, 1, 3, 3, 1);
TestSubscriber subscriber = new TestSubscriber();

Observable distinctObservable = sourceObservable.distinctUntilChanged();

distinctObservable.subscribe(subscriber);

subscriber.assertValues(1, 2, 1, 3, 1);

3.5. O operadorignoreElements

Sempre que quisermos ignorar todos os elementos emitidos pelosourceObservable, podemos simplesmente usar oignoreElements:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable ignoredObservable = sourceObservable.ignoreElements();

ignoredObservable.subscribe(subscriber);

subscriber.assertNoValues();

4. Operadores de filtragem de tempo

Ao trabalhar com sequência observável, o eixo do tempo é desconhecido, mas às vezes obter dados oportunos de uma sequência pode ser útil.

Com este propósito,RxJava offers a few methods that allow us to work with Observable using also the time axis.

Antes de passar para o primeiro, vamos definir umObservable cronometrado que emitirá um item a cada segundo:

TestScheduler testScheduler = new TestScheduler();

Observable timedObservable = Observable
  .just(1, 2, 3, 4, 5, 6)
  .zipWith(Observable.interval(
    0, 1, TimeUnit.SECONDS, testScheduler), (item, time) -> item);

The TestScheduler is a special scheduler that allows advancing the clock manually no ritmo que preferirmos.

4.1. Operadores desample ethrottleLast

O operadorsample filtratimedObservable, retornando umObservable que emite os itens mais recentes emitidos por esta API em intervalos de tempo.

Vamos ver como podemos amostrar otimedObservable, filtrando apenas o último item emitido a cada 2,5 segundos:

TestSubscriber subscriber = new TestSubscriber();

Observable sampledObservable = timedObservable
  .sample(2500L, TimeUnit.MILLISECONDS, testScheduler);

sampledObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertValues(3, 5, 6);

Esse tipo de comportamento também pode ser obtido usando o operadorthrottleLast.

4.2. O operadorthrottleFirst

O operadorthrottleFirst difere dethrottleLast/sample porque emite o primeiro item emitido portimedObservable em cada período de amostragem, em vez do último item emitido.

Vamos ver como podemos emitir os primeiros itens, usando um período de amostragem de 4 segundos:

TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = timedObservable
  .throttleFirst(4100L, TimeUnit.SECONDS, testScheduler);

filteredObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertValues(1, 6);

4.3. Operadores dedebounce ethrottleWithTimeout

Com o operadordebounce, é possível emitir apenas um item se um determinado intervalo de tempo passou sem emitir outro item.

Portanto, se selecionarmos um intervalo de tempo maior que o intervalo de tempo entre os itens emitidos detimedObservable, ele emitirá apenas o último. Por outro lado, se for menor, emitirá todos os itens emitidos pelotimedObservable.

Vamos ver o que acontece no primeiro cenário:

TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = timedObservable
  .debounce(2000L, TimeUnit.MILLISECONDS, testScheduler);

filteredObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertValue(6);

Esse tipo de comportamento também pode ser obtido usandothrottleWithTimeout.

4.4. O operadortimeout

O operadortimeout espelha a fonteObservable, mas emite um erro de notificação, cancelando a emissão de itens, se a fonteObservable não emitir nenhum item durante um intervalo de tempo especificado.

Vamos ver o que acontece se especificarmos um tempo limite de 500 milissegundos para nossotimedObservable:

TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = timedObservable
  .timeout(500L, TimeUnit.MILLISECONDS, testScheduler);

filteredObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertError(TimeoutException.class); subscriber.assertValues(1);

5. Filtragem Múltipla Observável

Ao trabalhar comObservable, é definitivamente possível decidir se deve filtrar ou pular itens com base em um segundoObservable.

Antes de prosseguir, vamos definir umdelayedObservable, que emitirá apenas 1 item após 3 segundos:

Observable delayedObservable = Observable.just(1)
  .delay(3, TimeUnit.SECONDS, testScheduler);

Vamos começar com o operadortakeUntil.

5.1. O operadortakeUntil

O operadortakeUntil descarta qualquer item emitido pela fonteObservable (timedObservable) após um segundoObservable (delayedObservable) emitir um item ou encerrar:

TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = timedObservable
  .skipUntil(delayedObservable);

filteredObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertValues(4, 5, 6);

5.2. O operadorskipUntil

Por outro lado,skipUntil descarta qualquer item emitido pela fonteObservable (timedObservable) até que um segundoObservable (delayedObservable) emita um item:

TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = timedObservable
  .takeUntil(delayedObservable);

filteredObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertValues(1, 2, 3);

6. Conclusão

Neste extenso tutorial, exploramos os diferentes operadores de filtragem disponíveis no RxJava, fornecendo um exemplo simples de cada um.

Como sempre, todos os exemplos de código neste artigo podem ser encontradosover on GitHub.