StateFlow¶
Un StateFlow es un tipus especial de Flow dissenyat per representar un estat que canvia al llarg del temps. A diferencia d'un Flow normal, un StateFlow sempre te un valor actual i nomes emet quan el valor canvia.
Documentacio oficial: StateFlow and SharedFlow - Android Developers
1. Caracteristiques principals¶
- Sempre te un valor actual: requereix un valor inicial al crear-lo.
- No emet duplicats consecutius: si s'assigna el mateix valor, no notifica als col·lectors.
- Es calent (hot): mante el valor encara que no hi hagi col·lectors actius, a diferencia dels Flows normals que son freds (cold).
- Ideal per a la UI: representa l'estat actual de la pantalla al ViewModel.
2. MutableStateFlow vs StateFlow¶
Segueix el mateix patro que MutableLiveData / LiveData:
MutableStateFlow: permet modificar el valor. Es mante privat dins del ViewModel.StateFlow: nomes lectura. S'exposa public perque la UI nomes pugui observar.
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class ComptadorViewModel : ViewModel() {
private val _comptador = MutableStateFlow(0)
val comptador: StateFlow<Int> = _comptador
fun incrementar() {
_comptador.value++
}
}
3. Observar un StateFlow des de la UI¶
Per recollir els valors d'un StateFlow cal fer-ho dins d'una coroutine, normalment amb lifecycleScope:
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class ComptadorActivity : AppCompatActivity() {
private val viewModel: ComptadorViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_comptador)
lifecycleScope.launch {
viewModel.comptador.collect { valor ->
binding.tvComptador.text = "Comptador: $valor"
}
}
}
}
Multiples col·lectors
Si necessites observar diversos StateFlows, cal llançar un launch separat per a cada collect, ja que collect suspen la coroutine fins que el Flow finalitza.
lifecycleScope.launch {
viewModel.comptador.collect { valor ->
binding.tvComptador.text = "Comptador: $valor"
}
}
lifecycleScope.launch {
viewModel.nomUsuari.collect { nom ->
binding.tvNom.text = nom
}
}
4. Convertir un Flow a StateFlow amb stateIn¶
Quan treballem amb un Flow normal (per exemple, el que retorna DataStore o Room) i el volem exposar com a StateFlow des del ViewModel, utilitzem l'operador stateIn:
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
class SettingsViewModel(application: Application) : AndroidViewModel(application) {
private val preferencesManager = PreferencesManager(application)
val nomUsuari: StateFlow<String> = preferencesManager.nomUsuari.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = ""
)
}
Parametres de stateIn¶
| Parametre | Descripcio |
|---|---|
scope |
El CoroutineScope on s'executa el Flow. Normalment viewModelScope |
started |
Quan comença a col·lectar. WhileSubscribed(5000) mante la subscripcio 5 segons despres que l'ultim observador desaparegui |
initialValue |
Valor inicial mentre el Flow encara no ha emes cap valor |
Per que WhileSubscribed(5000)?
El parametre de 5000 ms dona un marge perque, en una rotacio de pantalla, l'Activity es destrueix i es recrea rapidament. Sense aquest marge, el Flow es cancelaria i es reiniciaria innecessariament.
5. StateFlow vs LiveData¶
| Caracteristica | StateFlow | LiveData |
|---|---|---|
| Valor inicial | Obligatori | Opcional |
| Lifecycle-aware | No (cal lifecycleScope) |
Si, automatic |
| Duplicats consecutius | No emet | Si emet |
| Funciona fora d'Android | Si (Kotlin pur) | No |
| Operadors de transformacio | Tots els de Flow (map, filter, combine...) |
Limitats |
| Fil d'emissio | Qualsevol | Nomes fil principal |
6. Resum¶
| Concepte | Us |
|---|---|
MutableStateFlow |
Estat mutable dins del ViewModel |
StateFlow |
Estat immutable exposat a la UI |
stateIn |
Convertir un Flow fred a StateFlow |
collect |
Observar els canvis des de la UI |