Salta el contingut

Flows

Un Flow es un tipus de Kotlin que permet emetre una seqüencia de valors de forma asíncrona al llarg del temps. A diferencia d'una funcio suspend, que retorna un sol valor, un Flow pot emetre multiples valors de manera reactiva.

Els Flows formen part de la llibreria kotlinx.coroutines.flow i estan estretament lligats a les coroutines.

Documentacio oficial: Kotlin Flows - Android Developers

1. Per que serveixen els Flows?

En aplicacions Android, sovint necessitem observar dades que canvien al llarg del temps:

  • Preferencies de l'usuari que es modifiquen (per exemple, amb DataStore)
  • Resultats d'una consulta a la base de dades que s'actualitzen
  • Dades en temps real d'una API

Un Flow emet un nou valor cada cop que les dades canvien, i els observadors (collectors) reben automaticament l'actualitzacio.

2. Crear un Flow

Un Flow es crea amb el builder flow { }. Dins del bloc, s'utilitza emit() per enviar valors:

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.delay

fun comptador(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(1000) // espera 1 segon
        emit(i)     // emet el valor
    }
}

Flows son freds (cold)

Un Flow no s'executa fins que algu el col·lecta (collect). Cada cop que es crida collect, el Flow torna a començar des del principi.

3. Rebre valors amb collect

Per rebre els valors d'un Flow s'utilitza la funcio collect, que es una funcio suspend i per tant necessita executar-se dins d'una coroutine:

import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScope

lifecycleScope.launch {
    comptador().collect { valor ->
        println("Valor rebut: $valor")
    }
}

Sortida (un valor per segon):

Valor rebut: 1
Valor rebut: 2
Valor rebut: 3
Valor rebut: 4
Valor rebut: 5

collect es bloquejant

La funcio collect suspen la coroutine fins que el Flow finalitza. Si necessites col·lectar diversos Flows en paral·lel, cal llançar cada collect en una coroutine separada amb launch.

4. Operadors de transformacio

Els Flows es poden transformar amb operadors com map, filter o combine. Aquests operadors funcionen de forma similar als de les col·leccions de Kotlin, pero aplicats a fluxos asincrons.

import kotlinx.coroutines.flow.map

fun comptadorDoble(): Flow<Int> = comptador().map { it * 2 }

Per a una explicacio detallada d'aquests operadors, consulteu el document de col·leccions.

5. Flows vs LiveData

Tant Flow com LiveData permeten observar canvis en les dades, pero tenen diferencies importants:

Caracteristica LiveData Flow
Lifecycle-aware Si, automatic No directament (cal lifecycleScope)
Valor inicial Opcional No te
Operadors de transformacio Limitats (map, switchMap) Molt rics (map, filter, combine, zip...)
Funciona fora d'Android No (depèn del framework Android) Si (es Kotlin pur)
Emissio Nomes al fil principal Des de qualsevol fil
Reactivitat Un sol valor actiu Seqüencia de valors al llarg del temps

Quan utilitzar cada un?

  • LiveData: Segueix sent valid per exposar dades del ViewModel a la UI de forma senzilla. Es especialment util quan no cal fer transformacions complexes.
  • Flow: Recomanat per a fonts de dades (repositoris, DataStore, Room) i quan calen transformacions avançades. Al ViewModel, es pot convertir a StateFlow per exposar-lo a la UI.

Exemple comparatiu

Amb LiveData:

// ViewModel
private val _nom = MutableLiveData<String>("")
val nom: LiveData<String> = _nom

// Activity
viewModel.nom.observe(this) { valor ->
    binding.tvNom.text = valor
}

Amb Flow (convertit a StateFlow):

// ViewModel
private val _nom = MutableStateFlow("")
val nom: StateFlow<String> = _nom

// Activity
lifecycleScope.launch {
    viewModel.nom.collect { valor ->
        binding.tvNom.text = valor
    }
}

Per a mes informacio sobre StateFlow, consulteu el document de StateFlow.