Skip to Content

Flow và StateFlow trong Android

1. Giới thiệu

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

2. Flow cơ bản

Tạo Flow

// 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()

Collect Flow

viewModelScope.launch { numbersFlow.collect { value -> println(value) } }

3. Flow Operators

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) }

4. StateFlow

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 } } }

5. SharedFlow

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() }

6. StateFlow vs LiveData

FeatureStateFlowLiveData
Initial valueRequiredOptional
NullabilityNullableNullable
Lifecycle awareManualAuto
OperatorsRichLimited
TestingEasyNeeds testutils

7. Collect trong Compose

@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") } } }

Collect với Lifecycle

@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 } } } } }

8. Flow to StateFlow

class UserViewModel(private val repository: UserRepository) : ViewModel() { val users: StateFlow<List<User>> = repository.getUsersFlow() .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) }

SharingStarted options

// 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 )

9. Room with Flow

@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() }

10. Error Handling

repository.getDataFlow() .catch { exception -> emit(Result.Error(exception)) } .onEach { data -> _uiState.value = UiState.Success(data) } .launchIn(viewModelScope)

11. UI State Pattern

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) } } } } }

📝 Tóm tắt

TypeUse Case
FlowCold stream, database queries
StateFlowUI state, always has value
SharedFlowEvents, one-time actions
OperatorMô tả
mapTransform items
filterFilter items
collectConsume flow
stateInConvert to StateFlow
combineMerge multiple flows
Last updated on