Flow - Luồng dữ liệu
Flow là cách Kotlin xử lý luồng dữ liệu bất đồng bộ. Nếu coroutines là để xử lý một giá trị async, Flow là để xử lý nhiều giá trị theo thời gian.
Flow là gì?
Phần tiêu đề “Flow là gì?”// suspend function: Trả về MỘT giá trịsuspend fun fetchUser(): User
// Flow: Trả về NHIỀU giá trị theo thời gianfun observeUsers(): Flow<User>Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”| Use Case | Suspend Function | Flow |
|---|---|---|
| Load user | getUser() |
- |
| Real-time updates | - | observeUser() |
| Search suggestions | - | searchFlow |
| Location updates | - | locationFlow |
| Database changes | - | getAllUsersFlow() |
Tạo Flow
Phần tiêu đề “Tạo Flow”1. flow { } builder
Phần tiêu đề “1. flow { } builder”fun simpleFlow(): Flow<Int> = flow { for (i in 1..3) { delay(100) // Suspend emit(i) // Phát ra giá trị }}2. flowOf() - Từ values
Phần tiêu đề “2. flowOf() - Từ values”val numbersFlow = flowOf(1, 2, 3, 4, 5)3. asFlow() - Từ collections
Phần tiêu đề “3. asFlow() - Từ collections”val listFlow = listOf("A", "B", "C").asFlow()val rangeFlow = (1..10).asFlow()4. Flow từ callback
Phần tiêu đề “4. Flow từ callback”fun locationFlow(): Flow<Location> = callbackFlow { val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { trySend(result.lastLocation) // Phát ra location } }
locationClient.requestLocationUpdates(request, callback, Looper.getMainLooper())
awaitClose { // Cleanup khi flow bị cancel locationClient.removeLocationUpdates(callback) }}Collect Flow
Phần tiêu đề “Collect Flow”Flow chỉ chạy khi được collect (cold stream).
Cách collect
Phần tiêu đề “Cách collect”fun main() = runBlocking { simpleFlow().collect { value -> println("Received: $value") }}// Output:// Received: 1// Received: 2// Received: 3Các hàm terminal khác
Phần tiêu đề “Các hàm terminal khác”val flow = flowOf(1, 2, 3, 4, 5)
// Lấy tất cả thành listval list = flow.toList() // [1, 2, 3, 4, 5]
// Lấy giá trị đầuval first = flow.first() // 1
// Lấy giá trị cuốival last = flow.last() // 5
// Đếmval count = flow.count() // 5
// Reduceval sum = flow.reduce { acc, value -> acc + value } // 15Flow Operators
Phần tiêu đề “Flow Operators”Intermediate Operators
Phần tiêu đề “Intermediate Operators”val flow = (1..10).asFlow()
// map - Transform từng giá trịflow.map { it * 2 } // 2, 4, 6, 8, ...
// filter - Lọc giá trịflow.filter { it % 2 == 0 } // 2, 4, 6, 8, 10
// take - Lấy n giá trị đầuflow.take(3) // 1, 2, 3
// drop - Bỏ n giá trị đầuflow.drop(3) // 4, 5, 6, 7, 8, 9, 10
// transform - Custom transformationflow.transform { value -> emit(value) emit(value * 10)} // 1, 10, 2, 20, 3, 30, ...Chaining operators
Phần tiêu đề “Chaining operators”(1..100).asFlow() .filter { it % 2 == 0 } // Số chẵn .map { it * it } // Bình phương .take(5) // Lấy 5 đầu .collect { println(it) } // 4, 16, 36, 64, 100flowOn - Chuyển Dispatcher
Phần tiêu đề “flowOn - Chuyển Dispatcher”fun userFlow(): Flow<User> = flow { // Runs on IO val user = api.getUser() emit(user)}.flowOn(Dispatchers.IO) // Upstream chạy trên IO
// Collect chạy trên caller's contextviewModelScope.launch { userFlow().collect { user -> // Runs on Main (caller's context) updateUI(user) }}Lưu ý: flowOn chỉ ảnh hưởng upstream (code trước nó), không ảnh hưởng collect.
StateFlow và SharedFlow
Phần tiêu đề “StateFlow và SharedFlow”StateFlow
Phần tiêu đề “StateFlow”StateFlow lưu trữ giá trị hiện tại và emit cho collectors mới.
class CounterViewModel : ViewModel() {
// MutableStateFlow với initial value private val _count = MutableStateFlow(0)
// Public read-only StateFlow val count: StateFlow<Int> = _count.asStateFlow()
fun increment() { _count.value++ }}
// Collect trong UI@Composablefun CounterScreen(viewModel: CounterViewModel) { val count by viewModel.count.collectAsState()
Text("Count: $count")}StateFlow vs LiveData
Phần tiêu đề “StateFlow vs LiveData”| Feature | StateFlow | LiveData |
|---|---|---|
| Nullable | Không null mặc định | Nullable |
| Initial value | Bắt buộc | Optional |
| Lifecycle-aware | Manual | Tự động |
| Operators | Nhiều (map, filter…) | Ít |
| Kotlin-first | ✅ | ❌ (Java) |
SharedFlow
Phần tiêu đề “SharedFlow”SharedFlow cho phép multiple emissions mà không cần giá trị ban đầu.
class EventViewModel : ViewModel() {
private val _events = MutableSharedFlow<UiEvent>() val events: SharedFlow<UiEvent> = _events.asSharedFlow()
fun showToast(message: String) { viewModelScope.launch { _events.emit(UiEvent.ShowToast(message)) } }}
sealed class UiEvent { data class ShowToast(val message: String) : UiEvent() object NavigateBack : UiEvent()}Collect trong Android
Phần tiêu đề “Collect trong Android”Lifecycle-aware collecting
Phần tiêu đề “Lifecycle-aware collecting”// ❌ SAI - Tiếp tục collect khi app ở backgroundlifecycleScope.launch { viewModel.uiState.collect { state -> updateUI(state) }}
// ✅ ĐÚNG - Chỉ collect khi STARTEDlifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> updateUI(state) } }}Trong Compose
Phần tiêu đề “Trong Compose”@Composablefun UserScreen(viewModel: UserViewModel) { val uiState by viewModel.uiState.collectAsState()
// Hoặc với lifecycle-awareness val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when (uiState) { is UiState.Loading -> LoadingScreen() is UiState.Success -> UserContent(uiState.data) is UiState.Error -> ErrorScreen(uiState.message) }}Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Search với debounce
Phần tiêu đề “Search với debounce”class SearchViewModel : ViewModel() {
private val searchQuery = MutableStateFlow("")
val searchResults: StateFlow<List<Result>> = searchQuery .debounce(300) // Đợi 300ms không có input mới .filter { it.length >= 2 } // Ít nhất 2 ký tự .distinctUntilChanged() // Không search lại nếu query giống .flatMapLatest { query -> // Cancel search cũ nếu có query mới searchRepository.search(query) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() )
fun onQueryChanged(query: String) { searchQuery.value = query }}Database real-time updates
Phần tiêu đề “Database real-time updates”@Daointerface UserDao { @Query("SELECT * FROM users") fun observeAllUsers(): Flow<List<User>> // Room tự emit khi data thay đổi}
class UserRepository(private val dao: UserDao) {
fun observeUsers(): Flow<List<User>> = dao.observeAllUsers() .flowOn(Dispatchers.IO)}
// ViewModelclass UserListViewModel(private val repository: UserRepository) : ViewModel() {
val users: StateFlow<List<User>> = repository.observeUsers() .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() )}📝 Tóm tắt
Phần tiêu đề “📝 Tóm tắt”| Concept | Mô tả |
|---|---|
Flow |
Cold stream, chạy khi collect |
emit() |
Phát ra giá trị |
collect() |
Nhận giá trị |
map, filter |
Transform operators |
StateFlow |
Hot flow với current value |
SharedFlow |
Hot flow cho events |
flowOn |
Chuyển dispatcher |
stateIn |
Convert Flow → StateFlow |
Best Practices
Phần tiêu đề “Best Practices”- Dùng StateFlow cho UI state
- Dùng SharedFlow cho one-time events
- flowOn(Dispatchers.IO) cho network/database
- collectAsStateWithLifecycle trong Compose
- repeatOnLifecycle trong Views
Xem thêm
Phần tiêu đề “Xem thêm”- Flow trong Android - Chi tiết hơn về Android integration