Introdução ao RxJava

Introdução ao RxJava

*1. Visão geral *

Neste artigo, vamos nos concentrar no uso de Extensões Reativas (Rx) em Java para compor e consumir seqüências de dados.

À primeira vista, a API pode parecer semelhante ao Java 8 Streams, mas, na verdade, é muito mais flexível e fluente, tornando-o um poderoso paradigma de programação.

Se você quiser ler mais sobre o RxJava, consulte o link:/rxjava-backpressure [esta redação].

===* 2. Configuração *

Para usar o RxJava em nosso projeto Maven, precisamos adicionar a seguinte dependência ao nosso _pom .xml: _

<dependency>
    <groupId>io.reactivex</groupId>
    <artifactId>rxjava</artifactId>
    <version>${rx.java.version}</version>
</dependency>

Ou, para um projeto Gradle:

compile 'io.reactivex.rxjava:rxjava:x.y.z'

===* 3. Conceitos funcionais reativos *

Por um lado, a programação funcional é o processo de construção de software, compondo funções puras, evitando estado compartilhado, dados mutáveis ​​e efeitos colaterais.

Por outro lado, a programação reativa é um paradigma de programação assíncrono relacionado aos fluxos de dados e à propagação da mudança.

Juntas, a programação reativa funcional forma uma combinação de técnicas funcionais e reativas que podem representar uma abordagem elegante à programação orientada a eventos - com valores que mudam ao longo do tempo e em que o consumidor reage aos dados à medida que entram.

Essa tecnologia reúne diferentes implementações de seus princípios fundamentais. Alguns autores criaram um documento que define o vocabulário comum para descrever o novo tipo de aplicativos.

====* 3.1 Manifesto Reativo *

O Reative Manifesto é um documento on-line que estabelece um alto padrão para aplicativos na indústria de desenvolvimento de software. Simplificando, os sistemas reativos são:

  • Responsivo - os sistemas devem responder em tempo hábil

  • Orientado a mensagens - os sistemas devem usar passagem de mensagens assíncrona entre componentes para garantir um acoplamento fraco

  • Elástico - os sistemas devem permanecer responsivos sob alta carga *Resiliente - os sistemas devem permanecer responsivos quando alguns componentes falham

===* 4. Observáveis ​​*

Existem dois tipos de chaves para entender ao trabalhar com _Rx: _

  • Observable representa qualquer objeto que pode obter dados de uma fonte de dados e cujo estado pode ser de interesse de maneira que outros objetos possam registrar um interesse *Um observer é qualquer objeto que deseja ser notificado quando o estado de outro objeto mudar

Um observer assina uma sequência Observable.* A sequência envia itens para o observer, um de cada vez. *

O observer lida com cada um antes de processar o próximo. Se muitos eventos entrarem de forma assíncrona, eles deverão ser armazenados em uma fila ou descartados.

Em Rx, um observer nunca será chamado com um item fora de ordem ou antes que o retorno de chamada retorne para o item anterior.

====* 4.1 Tipos de Observável *

Existem dois tipos:

  • A execução assíncrona Non-Blocking – é suportada e tem permissão para cancelar a inscrição em qualquer ponto do fluxo de eventos. Neste artigo, focaremos principalmente nesse tipo de tipo

    *_Bloqueando - todas as chamadas de observador _onNext_ serão síncronas e não é possível cancelar a inscrição no meio de um fluxo de eventos. Sempre podemos converter um _Observable_ em um _Blocking Observable_, usando o método _toBlocking: _
BlockingObservable<String> blockingObservable = observable.toBlocking();

====* 4.2 Operadores *

*Um _operator_ é uma função que recebe um _Observable_ (a fonte) como seu primeiro argumento e retorna outro _Observable_ (o destino).* Então, para cada item que a fonte observável emita, ela aplicará uma função a esse item e depois emitirá o resultado no destino _Observable_.

Operators podem ser encadeados para criar fluxos de dados complexos que filtram eventos com base em determinados critérios. Vários operadores podem ser aplicados ao mesmo observable.

Não é difícil entrar em uma situação em que um Observável está emitindo itens mais rapidamente do que um operador ou um_observador_ pode consumi-los. Você pode ler mais sobre o link de contrapressão:/rxjava-backpressure [aqui.]

4.3 Criar Observável

O operador básico just produz um Observable que emite uma única instância genérica antes de concluir, a String _ “Hello” ._ Quando queremos obter informações de um Observable, implementamos uma interface observer e chamamos subscribe no _Observable desejado: _

Observable<String> observable = Observable.just("Hello");
observable.subscribe(s -> result = s);

assertTrue(result.equals("Hello"));

4.4 OnNext, OnError, _ e _OnCompleted

Existem três métodos na interface observer que queremos conhecer:

  1. OnNext é chamado em nosso observer toda vez que um novo evento é publicado no Observable anexado. Este é o método em que executaremos alguma ação em cada evento

  2. OnCompleted é chamado quando a sequência de eventos associada a um Observable é concluída, indicando que não devemos esperar mais chamadas onNext em nosso observador

  3. OnError é chamado quando uma exceção não tratada é lançada durante o código da estrutura RxJava ou nosso código de manipulação de eventos

O valor de retorno para o método Observables subscribe é uma interface subscribe:

String[] letters = {"a", "b", "c", "d", "e", "f", "g"};
Observable<String> observable = Observable.from(letters);
observable.subscribe(
  i -> result += i, //OnNext
  Throwable::printStackTrace,//OnError
  () -> result += "_Completed"//OnCompleted
);
assertTrue(result.equals("abcdefg_Completed"));

*5. Transformações observáveis ​​e operadores condicionais *

====* 5.1. Mapa *

O operador map transforma itens emitidos por um Observable aplicando uma função a cada item.

Vamos supor que exista uma matriz declarada de seqüências de caracteres que contém algumas letras do alfabeto e queremos imprimi-las no modo maiúsculo:

Observable.from(letters)
  .map(String::toUpperCase)
  .subscribe(letter -> result += letter);
assertTrue(result.equals("ABCDEFG"));

The flatMap pode ser usado para achatar Observables sempre que terminamos com Observables aninhados.

Mais detalhes sobre a diferença entre map e flatMap podem ser encontrados no link:/java-different-map-and-flatmap [aqui].

Supondo que temos um método que retorna um Observable <> de uma lista de strings. Agora, imprimiremos para cada string de um novo _Observable a lista de títulos com base no que o Subscriber vê:

Observable<String> getTitle() {
    return Observable.from(titleList);
}
Observable.just("book1", "book2")
  .flatMap(s -> getTitle())
  .subscribe(l -> result += l);

assertTrue(result.equals("titletitle"));

====* 5.2 Varredura *

O operador _scan aplica uma função a cada item emitido por um _Observable sequencialmente e emite cada valor sucessivo.

Ele nos permite transportar o estado de evento para evento:

String[] letters = {"a", "b", "c"};
Observable.from(letters)
  .scan(new StringBuilder(), StringBuilder::append)
  .subscribe(total -> result += total.toString());
assertTrue(result.equals("aababc"));

====* 5.3. GroupBy *

O operador Grupo por nos permite classificar os eventos na entrada Observável em categorias de saída.

Vamos supor que criamos uma matriz de números inteiros de 0 a 10 e aplicamos group by que os dividirá nas categorias even e odd:

Observable.from(numbers)
  .groupBy(i -> 0 == (i % 2) ? "EVEN" : "ODD")
  .subscribe(group ->
    group.subscribe((number) -> {
        if (group.getKey().toString().equals("EVEN")) {
            EVEN[0] += number;
        } else {
            ODD[0] += number;
        }
    })
  );
assertTrue(EVEN[0].equals("0246810"));
assertTrue(ODD[0].equals("13579"));

====* 5.4 Filtro *

O operador filter emite apenas os itens de um observable que passam no teste predicate.

Então, vamos filtrar em uma matriz inteira os números ímpares:

Observable.from(numbers)
  .filter(i -> (i % 2 == 1))
  .subscribe(i -> result += i);

assertTrue(result.equals("13579"));

====* 5.5. Operadores condicionais *

DefaultIfEmpty emite item da origem Observable, ou um item padrão se a origem Observable estiver vazia:

Observable.empty()
  .defaultIfEmpty("Observable is empty")
  .subscribe(s -> result += s);

assertTrue(result.equals("Observable is empty"));

O código a seguir emite a primeira letra do alfabeto ‘a' porque o array letters não está vazio e é isso que ele contém na primeira posição:

Observable.from(letters)
  .defaultIfEmpty("Observable is empty")
  .first()
  .subscribe(s -> result += s);

assertTrue(result.equals("a"));

O operador TakeWhile descarta itens emitidos por um Observable após uma condição especificada se tornar falsa:

Observable.from(numbers)
  .takeWhile(i -> i < 5)
  .subscribe(s -> sum[0] += s);

assertTrue(sum[0] == 10);

Claro, existem outros operadores que podem atender às nossas necessidades, como _Contain, SkipWhile, SkipUntil, TakeUntil, _ etc.

===* 6. Observáveis ​​conectáveis ​​*

*Um _ConnectableObservable_ se assemelha a um _Observable_ comum, exceto que ele não começa a emitir itens quando está inscrito, mas apenas quando o operador _connect_ é aplicado a ele.*

Dessa maneira, podemos esperar que todos os observadores pretendidos se inscrevam no Observable antes que o Observable comece a emitir itens:

String[] result = {""};
ConnectableObservable<Long> connectable
  = Observable.interval(200, TimeUnit.MILLISECONDS).publish();
connectable.subscribe(i -> result[0] += i);
assertFalse(result[0].equals("01"));

connectable.connect();
Thread.sleep(500);

assertTrue(result[0].equals("01"));

7. solteiro

Single é como um Observable que, em vez de emitir uma série de valores, emite um valor ou uma notificação de erro.

Com essa fonte de dados, podemos usar apenas dois métodos para se inscrever:

  • OnSuccess retorna um Single que também chama um método que especificamos *OnError também retorna um Single que notifica imediatamente os assinantes de um erro

String[] result = {""};
Single<String> single = Observable.just("Hello")
  .toSingle()
  .doOnSuccess(i -> result[0] += i)
  .doOnError(error -> {
      throw new RuntimeException(error.getMessage());
  });
single.subscribe();

assertTrue(result[0].equals("Hello"));

===* 8. Assuntos *

Um Subject é simultaneamente dois elementos, um subscriber e um observable. Como assinante, um assunto pode ser usado para publicar os eventos provenientes de mais de um observável.

E como também é observável, os eventos de vários assinantes podem ser reemitidos como eventos para qualquer pessoa que o observe.

No próximo exemplo, veremos como os observadores poderão ver os eventos que ocorrem após a assinatura:

Integer subscriber1 = 0;
Integer subscriber2 = 0;
Observer<Integer> getFirstObserver() {
    return new Observer<Integer>() {
        @Override
        public void onNext(Integer value) {
           subscriber1 += value;
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("error");
        }

        @Override
        public void onCompleted() {
            System.out.println("Subscriber1 completed");
        }
    };
}

Observer<Integer> getSecondObserver() {
    return new Observer<Integer>() {
        @Override
        public void onNext(Integer value) {
            subscriber2 += value;
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("error");
        }

        @Override
        public void onCompleted() {
            System.out.println("Subscriber2 completed");
        }
    };
}

PublishSubject<Integer> subject = PublishSubject.create();
subject.subscribe(getFirstObserver());
subject.onNext(1);
subject.onNext(2);
subject.onNext(3);
subject.subscribe(getSecondObserver());
subject.onNext(4);
subject.onCompleted();

assertTrue(subscriber1 + subscriber2 == 14)

===* 9. Gestão de recursos *

A operação Usando nos permite associar recursos, como uma conexão de banco de dados JDBC, uma conexão de rede ou abrir arquivos para nossos observáveis.

Aqui, apresentamos nos comentários as etapas que precisamos fazer para alcançar esse objetivo e também um exemplo de implementação:

String[] result = {""};
Observable<Character> values = Observable.using(
  () -> "MyResource",
  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[0] += v,
  e -> result[0] += e
);
assertTrue(result[0].equals("MyResource"));

===* 10. Conclusão*

Neste artigo, falamos sobre como usar a biblioteca RxJava e também sobre como explorar seus recursos mais importantes.

O código fonte completo do projeto, incluindo todas as amostras de código usadas aqui, pode ser encontrado em over no Github.