Bỏ qua để đến nội dung

Flow và StateFlow

Flow là cold asynchronous stream của Kotlin, tương tự RxJava Observable nhưng đơn giản hơn.

// flow builder
val numbersFlow: Flow<Int> = flow {
for (i in 1..5) {
delay(100)
emit(i)
}
}
// flowOf
val simpleFlow = flowOf(1, 2, 3)
// asFlow
val listFlow = listOf(1, 2, 3).asFlow()
viewModelScope.launch {
numbersFlow.collect { value ->
println(value)
}
}
val flow = flowOf(1, 2, 3, 4, 5)
// map
flow.map { it * 2 } // 2, 4, 6, 8, 10
// filter
flow.filter { it % 2 == 0 } // 2, 4
// take
flow.take(3) // 1, 2, 3
// debounce (useful for search)
searchQueryFlow
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { query ->
search(query)
}
// combine
combine(flow1, flow2) { a, b ->
a + b
}
// zip
flow1.zip(flow2) { a, b ->
Pair(a, b)
}

Cold vs Hot:

  • Flow: Cold - emit khi có collector
  • StateFlow: Hot - luôn có giá trị hiện tại
class CounterViewModel : ViewModel() {
// MutableStateFlow cho internal use
private val _count = MutableStateFlow(0)
// StateFlow cho external use (read-only)
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() {
_count.value++
// hoặc
_count.update { it + 1 }
}
}

Dùng cho events (không có initial value):

class EventViewModel : ViewModel() {
private val _events = MutableSharedFlow<UiEvent>()
val events: SharedFlow<UiEvent> = _events.asSharedFlow()
fun showSnackbar(message: String) {
viewModelScope.launch {
_events.emit(UiEvent.ShowSnackbar(message))
}
}
}
sealed class UiEvent {
data class ShowSnackbar(val message: String) : UiEvent()
object NavigateBack : UiEvent()
}
Feature StateFlow LiveData
Initial value Required Optional
Nullability Nullable Nullable
Lifecycle aware Manual Auto
Operators Rich Limited
Testing Easy Needs testutils
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
// collectAsState cho StateFlow
val count by viewModel.count.collectAsState()
Column {
Text("Count: $count")
Button(onClick = { viewModel.increment() }) {
Text("Increment")
}
}
}
@Composable
fun EventHandler(viewModel: EventViewModel) {
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
viewModel.events.collect { event ->
when (event) {
is UiEvent.ShowSnackbar -> {
snackbarHostState.showSnackbar(event.message)
}
is UiEvent.NavigateBack -> {
// handle
}
}
}
}
}
class UserViewModel(private val repository: UserRepository) : ViewModel() {
val users: StateFlow<List<User>> = repository.getUsersFlow()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
}
// Lazily: Bắt đầu khi có subscriber đầu tiên
SharingStarted.Lazily
// Eagerly: Bắt đầu ngay lập tức
SharingStarted.Eagerly
// WhileSubscribed: Active khi có subscribers
SharingStarted.WhileSubscribed(
stopTimeoutMillis = 5000,
replayExpirationMillis = Long.MAX_VALUE
)
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>>
}
class UserRepository(private val userDao: UserDao) {
fun getUsers(): Flow<List<User>> = userDao.getAllUsers()
}
repository.getDataFlow()
.catch { exception ->
emit(Result.Error(exception))
}
.onEach { data ->
_uiState.value = UiState.Success(data)
}
.launchIn(viewModelScope)
data class HomeUiState(
val isLoading: Boolean = false,
val users: List<User> = emptyList(),
val error: String? = null
)
class HomeViewModel(private val repository: UserRepository) : ViewModel() {
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
init {
loadUsers()
}
private fun loadUsers() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
repository.getUsers()
.onSuccess { users ->
_uiState.update {
it.copy(isLoading = false, users = users)
}
}
.onFailure { error ->
_uiState.update {
it.copy(isLoading = false, error = error.message)
}
}
}
}
}

Type Use Case
Flow Cold stream, database queries
StateFlow UI state, always has value
SharedFlow Events, one-time actions
Operator Mô tả
map Transform items
filter Filter items
collect Consume flow
stateIn Convert to StateFlow
combine Merge multiple flows