RxJava and Kotlin #4

Transformando objetos dentro de uma mesma chain

Bruno Hensel
Published in
6 min readApr 14, 2021

--

No capítulo passado você viu os operadores destinados a separar as datas que não são desejadas navegarem mais adiante na stream, e se tornou um expert quando o assunto é filtering operators.

Neste episódio você verá os operadores mais utilizados para transformar uma data em outra. Você vai perceber que, utilizando o RxJava no dia-a-dia, os operadores mais utilizados são aqueles destinados à modelar a data que chega de um observable para posteriormente ser consumida pelo subscritor.

Map operator:

O operador map do RxJava funciona exatamente como o operador map do Kotlin, com a exceção dele operar sobre um observable ao invés de uma collection. Segundo o gráfico ao lado, a função map recebe um lambda como parâmetro, onde dentro do bloco você terá acesso ao it, que nada mais é do que a representação do item emitido, e poderá a parti daí transformar seu item. Ao final, será retornado um observable que emitirá o resultado do que foi feito dentro do bloco.

Vamos ao exemplo.

Imagine que você faz um HTTP request para obter uma lista de usuários, então você terá um objeto ResponseDTO para fazer o parse de um JSON para sua data class, esse objeto terá como propriedade uma lista de usuários representados pelos objeto User e no meio do caminho você faz a conversão desse ResponseDTO para o estado que você observará na view, que nesse caso poderá ser um estado de vazio ou um estado de sucesso.

Assim você terá os seguintes data classes que modelam sua data:

data class ResponseDTO(val users: List<User>)
data class User(val name: String, val age: Int)

E um sealed class representando o estado da UI:

sealed class UIState {
data class FetchUserSuccess(val users: List<User>) : UIState()
object EmptyState : UIState()
}

Em alguma layer onde você encapsula a transformação de um reponse em um estado, você teria a seguinte função:

Aqui estou mocando o response na linha 3, nas linhas 4 e 5 eu altero a thread em que rola a subscrição e onde se observa o resultado (Schedulers serão abordados em um post próprio) e na linha 6 eu faço a transformação de um Observable<ResponseDTO> para um Observable<UIState>, onde será emitido um EmptyState se a lista de User for vazia, ou um FetchUserSuccess com uma lista de User caso contrário.

Geralmente eu costumo observar os estado que chegam na View da seguinte forma:

Nas linhas de 1 a 3 eu subscrevo ao observable que é retornado da função getUserState() e na função renderState(state: UIState) eu checo qual estado chegou de fato na View e tomo as decisões destinadas e tratar cada estado.

FlatMap operator :

Segundo consta na documentação, esse operador transforma um observable emitido pelo observable source, aplicando uma função a cada item emitido (bloco do lambda) que, por si só, também emite novos eventos, e por fim, o FlatMap unifica (flatten) as emissões desses observables em uma única sequência.

Confuso? Vamos passo-a-passo analisar o gráfico acima.

Analisando o estritamente o bloco do FlatMap, percebemos que ele recebe um observable do tipo esfera e o transforma em um outro observable que emite losangulos. Nesse ponto você teria 2 observables, daí entra a mágica do operador que unifica esses observables em um só.

Especificidades:

  • FlatMap retorna/emite todos os intens recebidos.
  • Como o FlatMap acaba criando novos observables para cada item e cada observable tem sua própria vida (emitindo itens em intervalos diferentes), ele não é capaz de manter as mesma ordem na qual os itens chegaram.

Segundo a documentação, um bom use-case para o FlatMap se dá quando você tem um observable que emite um série de itens, cujos seus membro também são observables.

Aqui vai o passo-a-passo:

Inicialmente temos uma classe Animal que recebe em seu construtor um BehaviorSubject do tipo String:

data class Animal(val type: BehaviorSubject<String>)

Nas linhas 2 e 3 você criou duas instâncias da classe Animal, mammal e amphibians, na linha 4 temos a criação do nosso source subject do tipo Animal, na linha 7 você utiliza o FlatMap para acessar o tipo subject da classe Animal e ter acesso ao seu type, aqui nada é modificado, so jogamos o type stream abaixo até o subscritor e, por fim, na linha 8 printamos os tipos emitidos.

Na linha 12 eu utilizei coroutines para causar um delay de 10 ms para simular a demora de alguma chamada e também para demonstrar que o FlatMap não se interessa em manter a order das emissões.

Nas linhas 21 a 26 temos todas as emissões printadas, isso porque o FlatMap mantém todos os resultados de todos os observables que ele cria; vemos também que a ordem dos onNext’s não foi mantida.

SwitchMap operator :

A definição oficial desse operador é um pouco confusa, pelo menos pra mim. Ela esclarece mais ou menos assim:

Retorna um novo Observable aplicando uma função que você fornece a cada item emitido pelo ObservableSource atual que retorna um ObservableSource, e então emite os itens emitidos pelo ObservableSource mais recente emitido por estes ObservableSources.

Basicamente o que isso quer dizer é o seguinte: sempre que um novo item for emitido pelo ObservableSource, ele irá cancelar a subscrição e parar de espelhar o observable que foi gerado a partir do item anteriormente emitido, e começar a espelhar apenas o observable atual.

Analisando o gráfico acima:

Inicialmente percebemos que o ObservableSource emite itens do tipo esfera, e o switchMap os transforma em 1°- losangulo e 2° quadrado, emitindo cada transformação até o subscritor.

Os eventos para o circulo vermelho ocorreram sem nenhum problema, todas as mudanças chegaram até o subscritor.

Os eventos para o circulo verde são interessantes, pois, somente a primeira alteração chegou até o subscritor. Isso porque, durante a transformação de losângulo para quadrado, um novo item (circulo azul) foi emitido pelo ObservableSource, o que leva o switchMap a cancelar a subscrição do ciruclo verde, deixando de emitir seus itens, porque agora o mais novo observable é do tipo ciruclo azul. O switchMap só se importa com o observable mais atual.

Vamos analisar o mesmo exemplo do FlatMap, mas agora aplicando o SwitchMap.

A única diferença entre o exemplo do FlatMap é que aqui eu removi o delay(10) e o operador é o switchMap, claro ^^.

Percebemos que o animal “Sheep” mammal.type.onNext("Sheep") não está sendo emitido, isso se explica pelo fato de que o amphibians subject (linha 14) passou a ser emitido, deste ponto em diante, o switchMap só se importa em emitir os intens provenientes do amphibian subject.

TL;DR

Abordamos aqui 3 diferentes tipos de operadores destinados a transformar sua data. Esses são apenas os 3 que considero os mais comuns de serem usados, se você tiver interesse em olhar outros operadores, você pode recorrer à documentação oficial para transforming observables.

Sintetizando: Percebemos que o operador map, apenas mapeia, ou converte, um objeto de um tipo em um observable de outro tipo, o exemplo acima dado foi de Observable<ResponseDTO> para um Observable<UIState>.

FlatMap mantém toda a cadeia de observables a cada nova emissão e não liga para a ordem, desta forma, o subscritor sempre receberá todos os itens emitidos e na6o necessariamente na mesma ordem.

SwitchMap, por outro lado, embora se assemelhe com o FlatMap, não irá levar até o subscritor todos os itens emitidos, mas sim, somente os itens emitidos pelo observable mais atual.

Tem um artigo bem bacana sobre as diferenças entre FlatMap, SwitchMap e ConcatMap, escrito pelo Roque Buarque, vale a pena dar uma olhada lá.

Bom é isso, valeu por insistir e chegar até aqui, eu sei, não tem sido fácil! :) Se esse artigo te ajudou de alguma forma, não se esqueça de deixar um clapzinho aí, isso nos ajuda a manter o ritmo de artigos. Um abraço e até a próxima!

--

--