Fluxo Kotlin | Parte 2

Apr 19 2023
Neste artigo, entenderemos o fluxo do Kotlin em detalhes. Já está publicada a PARTE 1 deste artigo que pode visitar a partir daqui.
Foto de Marc Reichelt no Unsplash

Neste artigo, entenderemos o fluxo do Kotlin em detalhes.

Já está publicada a PARTE 1 deste artigo que pode visitar a partir daqui.

Fluxo Kotlin | Parte 1

Esta é a PARTE 2 da série que cobre os seguintes tópicos:

  • Cancelamento de Fluxo
  • Operadores de fluxo intermediário
  • Operador de transformação
  • Operador de limitação de tamanho
  • Operador de Fluxo Terminal

Em Kotlin, cancelamento de fluxo é o processo de interromper a execução de um fluxo quando ele não é mais necessário. Isso pode ser útil para evitar vazamentos de recursos e cálculos desnecessários.

Para cancelar um fluxo, você pode usar a cancel()função. Essa função é definida como uma função de extensão na Jobinterface, que é retornada pela launch()função quando você inicia uma co-rotina. Portanto, para cancelar um fluxo, você precisa ter uma referência ao Jobobjeto que foi retornado quando o fluxo foi lançado.

Aqui está um exemplo de como cancelar um fluxo:

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking<Unit> {
    val flow = flow {
        for (i in 1..10) {
            delay(100)
            emit(i)
        }
    }

    val job = launch {
        flow.collect { println(it) }
    }

    delay(500)
    job.cancel()
}

Após 500 milissegundos, chamamos a cancel()função no Jobobjeto retornado por launch(). Isso cancela o fluxo e interrompe quaisquer outras emissões. A co-rotina que estava coletando os valores de fluxo será cancelada e o programa será finalizado.

Observe que quando um fluxo é cancelado, o cancelamento é propagado a montante para a origem do fluxo. Neste exemplo, a delay()função dentro do fluxo lançará um CancellationException, que será capturado pela co-rotina que iniciou o fluxo.

Outras maneiras de cancelar o fluxo usando verificações de cancelamento de fluxo

  1. Loop ocupado emitindo valores canceláveis
  2. fun foo(): Flow<Int> = flow { 
        for (i in 1..5) {
            println("Emitting $i") 
            emit(i) 
        }
    }
    
    fun main() = runBlocking<Unit> {
        foo().collect { value -> 
            if (value == 3) cancel()  
            println(value)
        } 
    }
    

    Emitting 1
    1
    Emitting 2
    2
    Emitting 3
    3
    Emitting 4
    Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@6d7b4f4c
    

    
    fun main() = runBlocking<Unit> {
        (1..5).asFlow().collect { value -> 
            if (value == 3) cancel()  
            println(value)
        } 
    }
    1
    2
    3
    4
    5
    Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@3327bd23\
    

Outra forma de cancelar um fluxo é usar o cancellable()operador para tornar o fluxo cancelável de fora. Aqui está um exemplo:

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking<Unit> {
    val flow = flow {
        for (i in 1..10) {
            delay(100)
            emit(i)
        }
    }

    val job = launch {
        flow.cancellable().collect { println(it) }
    }

    delay(500)
    job.cancel()
}

Observe que o cancellable()operador na verdade não cancela o fluxo; simplesmente torna possível cancelar o fluxo de fora. O fluxo só será cancelado se a corrotina que está coletando os valores do fluxo também for cancelada.

Operadores de fluxo intermediário

Os fluxos podem ser transformados usando operadores, da mesma forma que você transformaria coleções e sequências. Operadores intermediários são aplicados a um fluxo a montante e retornam a um fluxo a jusante. Esses operadores são frios, assim como os fluxos. Uma chamada para tal operador não é uma função de suspensão em si. Funciona rapidamente, retornando a definição de um novo fluxo transformado.

  1. filter:

val numbers = flowOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// filter out even numbers
val filteredNumbers = numbers.filter { it % 2 == 0 }
filteredNumbers.collect { println(it) } // output: 2 4 6 8 10

O mapoperador transforma elementos em um fluxo usando uma função especificada. Por exemplo, você pode usar o mapoperador para converter temperaturas de Fahrenheit para Celsius.

val temperaturesFahrenheit = flowOf(68, 73, 82, 79, 64, 57)
val temperaturesCelsius = temperaturesFahrenheit.map { (it - 32) * 5/9 }
temperaturesCelsius.collect { println(it) }
 // output: 20.0 22.77777777777778 27.77777777777778 26.11111111111111 17.77777777777778 13.88888888888889

O flatMapConcatoperador transforma cada elemento de um fluxo em outro fluxo e concatena os fluxos resultantes em um único fluxo de saída. Por exemplo, você pode usar o flatMapConcatoperador para ler vários arquivos CSV e concatenar seu conteúdo em um único fluxo.

val filenames = flowOf("file1.csv", "file2.csv", "file3.csv")
// read each file and concatenate their contents into a single flow
val contents = filenames.flatMapConcat { filename ->
    flow {
        // read contents of the file
        val fileContents = readFile(filename)
        // emit each line of the file as a separate element in the flow
        fileContents.lines().forEach { emit(it) }
    }
}
contents.collect { println(it) } // output: contents of file1.csv, contents of file2.csv, contents of file3.csv

O distinctUntilChangedoperador remove elementos duplicados consecutivos de um fluxo. Por exemplo, você pode usar o distinctUntilChangedoperador para remover valores duplicados consecutivos de um fluxo de inteiros.

val numbers = flowOf(1, 2, 2, 3, 3, 3, 2, 2, 1)
// remove consecutive duplicates
val distinctNumbers = numbers.distinctUntilChanged()
distinctNumbers.collect { println(it) } // output: 1 2 3 2 1

O takeoperador limita o número de elementos em um fluxo a um máximo especificado. Por exemplo, você pode usar o takeoperador para limitar o número de elementos em um fluxo para 5.

val numbers = flowOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// limit to 5 elements
val limitedNumbers = numbers.take(5)
limitedNumbers.collect { println(it) } // output: 1 2 3 4 5

o transformoperador é usado para transformar os elementos emitidos por um Flow para produzir um novo Flow.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking<Unit> {
    val numbers = flowOf(1, 2, 3, 4, 5)
    
    numbers
        .transform { value ->
            emit("Value: $value")
            emit("Double value: ${value * 2}")
        }
        .collect { println(it) }
}
Value: 1
Double value: 2
Value: 2
Double value: 4
Value: 3
Double value: 6
Value: 4
Double value: 8
Value: 5
Double value: 10

No fluxo Kotlin, existem vários operadores que podem ser usados ​​para limitar o tamanho dos itens emitidos no fluxo.

O cancelamento em corrotinas é sempre realizado lançando uma exceção, para que todas as funções de gerenciamento de recursos (como try { ... } finally { ... }blocos) operem normalmente em caso de cancelamento.

  1. take(n: Int): este operador pega os primeiros nelementos do fluxo e cancela o fluxo. Se o fluxo produzir menos de nelementos, ele emitirá todos eles.
  2. takeWhile(predicate: (T) -> Boolean): Este operador obtém elementos do fluxo enquanto a função de predicado retorna true. Quando o predicado retorna false, o fluxo é cancelado.
  3. takeLast(n: Int): Este operador coleta os últimos nelementos emitidos pelo fluxo e os emite na ordem inversa.
  4. import kotlinx.coroutines.flow.flowOf
    import kotlinx.coroutines.flow.takeLast
    import kotlinx.coroutines.runBlocking
    
    fun main() = runBlocking {
        val numbers = flowOf(1, 2, 3, 4, 5)
        val lastThreeNumbers = numbers.takeLast(3)
        lastThreeNumbers.collect { println(it) } // prints 3, 4, 5
    }
    

    import kotlinx.coroutines.flow.flowOf
    import kotlinx.coroutines.flow.drop
    import kotlinx.coroutines.flow.collect
    import kotlinx.coroutines.runBlocking
    
    fun main() = runBlocking {
        val numbers = flowOf(1, 2, 3, 4, 5)
        numbers.drop(2).collect { println(it) } // prints 3, 4, 5
    }
    

Operador de Fluxo Terminal

Operadores de terminal são operadores em Kotlin Flow que consomem os elementos emitidos pelo fluxo e produzem um resultado. coletar é o mais usado.

  1. toList(): Este operador coleta todos os elementos emitidos pelo fluxo em uma lista e retorna a lista.
  2. toSet(): Este operador coleta todos os elementos emitidos pelo fluxo em um conjunto e retorna o conjunto.
  3. reduce(): Este operador aplica uma determinada função aos elementos emitidos pelo fluxo e retorna um único valor. A função recebe dois argumentos, sendo o primeiro o resultado acumulado até o momento e o segundo o próximo elemento emitido pelo fluxo.
  4. fold(): Este operador é semelhante ao reduce(), mas permite especificar um valor inicial para o resultado acumulado.
  5. count(): Este operador conta o número de elementos emitidos pelo fluxo e retorna a contagem como um inteiro.
  6. min(), max(): Esses operadores encontram o elemento mínimo ou máximo emitido pelo fluxo, respectivamente.
  7. collect(): Este operador consome os elementos emitidos pelo fluxo e executa uma ação em cada elemento. Ele não retorna um resultado, mas é frequentemente usado para efeitos colaterais.
  8. import kotlinx.coroutines.flow.flowOf
    import kotlinx.coroutines.flow.*
    import kotlinx.coroutines.runBlocking
    
    fun main() = runBlocking {
        val numbers = flowOf(1, 2, 3, 4, 5)
    
        val list = numbers.toList()
        println("List of numbers: $list")
    
        val set = numbers.toSet()
        println("Set of numbers: $set")
    
        val sum = numbers.reduce { accumulator, value -> accumulator + value }
        println("Sum of numbers: $sum")
    
        val product = numbers.fold(1) { accumulator, value -> accumulator * value }
        println("Product of numbers: $product")
    
        val count = numbers.count()
        println("Count of numbers: $count")
    
        val min = numbers.min()
        println("Minimum number: $min")
    
        val max = numbers.max()
        println("Maximum number: $max")
    
        numbers.collect { println("Processing $it") }
    }
    List of numbers: [1, 2, 3, 4, 5]
    Set of numbers: [1, 2, 3, 4, 5]
    Sum of numbers: 15
    Product of numbers: 120
    Count of numbers: 5
    Minimum number: 1
    Maximum number: 5
    Processing 1
    Processing 2
    Processing 3
    Processing 4
    Processing 5
    

Em conclusão, o Kotlin Flow é uma ferramenta poderosa para criar aplicativos reativos e assíncronos em Kotlin. Sua capacidade de lidar com fluxos de dados e sua integração com corrotinas o tornam uma excelente opção para lidar com operações assíncronas e gerenciar atualizações de interface do usuário. Com seu rico conjunto de operadores e API fácil de usar, os desenvolvedores podem criar facilmente pipelines de dados complexos que podem lidar com uma ampla variedade de cenários. O Kotlin Flow fornece uma maneira simples, concisa e intuitiva de gerenciar fluxos de dados em seus aplicativos Kotlin, tornando-o uma adição valiosa ao kit de ferramentas de qualquer desenvolvedor. Como o Kotlin continua a ganhar popularidade como linguagem de escolha para o desenvolvimento do Android, o Kotlin Flow certamente se tornará uma ferramenta indispensável para a criação de aplicativos robustos, eficientes e responsivos.

Espero que este artigo tenha sido útil para você.

Se você tiver algum comentário ou dúvida, escreva-me de volta para [email protected]. Suas palmas são muito apreciadas para ajudar outras pessoas a encontrar este artigo .