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
| Feature | StateFlow | LiveData |
|---|---|---|
| Initial value | Required | Optional |
| Nullability | Nullable | Nullable |
| Lifecycle aware | Manual | Auto |
| Operators | Rich | Limited |
| Testing | Easy | Needs 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
| 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 |
Last updated on