Kotlin Flow vs StateFlow vs SharedFlow: Câu hỏi phỏng vấn Android năm 2026
Những câu hỏi Kotlin Flow vs StateFlow vs SharedFlow mà nhà tuyển dụng Android hỏi năm 2026, kèm câu trả lời rõ ràng, một bảng so sánh và mã nguồn sẵn sàng cho production.

Kotlin Flow vs StateFlow vs SharedFlow là một trong những chủ đề phỏng vấn Android phổ biến nhất năm 2026, và việc nhầm lẫn ba khái niệm này là cách nhanh nhất để đánh mất vòng hỏi về coroutine. StateFlow và SharedFlow đều là các luồng hot được xây dựng trên nền Flow, nhưng chúng ra đời để giải quyết những bài toán khác nhau: lưu giữ trạng thái so với phát đi sự kiện. Những câu hỏi bên dưới là những gì nhà tuyển dụng Android thực sự hỏi, kèm theo câu trả lời chính xác và mã nguồn sẵn sàng cho môi trường production.
Một Flow là cold và chạy lại producer một lần cho mỗi collector. StateFlow là một luồng hot, được conflate, luôn giữ một giá trị hiện tại duy nhất, lý tưởng cho trạng thái UI. SharedFlow là một luồng hot không bắt buộc giá trị khởi tạo, lý tưởng cho các sự kiện một lần như điều hướng hay một snackbar.
Kotlin Flow vs StateFlow vs SharedFlow: những khác biệt cốt lõi
StateFlow và SharedFlow là các dạng chuyên biệt của SharedFlow, vốn bản thân là một Flow hot. Điều nhà tuyển dụng thăm dò là cold so với hot, liệu một giá trị hiện tại có được giữ lại hay không, và mỗi loại xử lý các lần phát trùng lặp ra sao. Một mô hình tư duy súc tích: Flow là một công thức, StateFlow là một giá trị mutable duy nhất, còn SharedFlow là một event bus.
| Thuộc tính | Flow (cold) | StateFlow | SharedFlow |
|---|---|---|---|
| Nhiệt độ | Cold | Hot | Hot |
| Giá trị khởi tạo | Không có | Bắt buộc | Tùy chọn (qua replay) |
| Giữ giá trị hiện tại | Không | Có, qua .value | Không |
| Phát giá trị trùng lặp | Có | Không (conflate + loại trùng) | Có thể cấu hình |
| Nhiều collector | Luồng mới mỗi lần | Dùng chung | Dùng chung |
| Phù hợp nhất cho | Pipeline dữ liệu bất đồng bộ | Trạng thái UI | Sự kiện một lần |
Tài liệu chính thức về Kotlin coroutines xem hành vi mặc định cold này là thuộc tính đặc trưng của Flow, nên một câu trả lời chắc chắn nên bắt đầu từ đó.
Tại sao Kotlin Flow mặc định là cold?
Một cold flow không làm gì cho đến khi collect() được gọi, và nó thực thi lại khối producer cho mỗi collector. Hai collector trên cùng một cold flow sẽ kích hoạt hai lời gọi mạng độc lập. Đây là khái niệm được kiểm tra nhiều nhất, nên một ví dụ ngắn sẽ làm rõ câu trả lời.
fun searchResults(query: String): Flow<List<Result>> = flow {
// This block runs fresh for every collector.
// Nothing executes until a collector calls collect().
val results = api.search(query) // suspending network call
emit(results) // pushed downstream to the collector
}Vì producer khởi động lại theo từng collector, cold flow là lựa chọn mặc định phù hợp cho các pipeline dữ liệu cần chạy theo yêu cầu. Chúng chỉ trở thành vấn đề khi một giá trị cần được chia sẻ trên toàn bộ một màn hình, và đó chính là lúc StateFlow và SharedFlow xuất hiện.
Câu hỏi phỏng vấn về StateFlow: trạng thái luôn phát ra
StateFlow là một luồng hot luôn có một giá trị và phát lại giá trị mới nhất đó cho mọi collector mới. Nó yêu cầu một giá trị khởi tạo, cung cấp .value để đọc đồng bộ, và conflate các lần phát: những cập nhật liên tiếp nhanh có thể bỏ qua các giá trị trung gian, và việc gán cùng một giá trị hai lần sẽ không phát gì vì nó loại trùng dựa trên đẳng thức. Điều này khiến nó trở thành sự thay thế hiện đại cho LiveData trong một ViewModel.
class SearchViewModel(private val repo: SearchRepository) : ViewModel() {
// MutableStateFlow demands an initial value, so the UI always has something to render.
private val _uiState = MutableStateFlow<SearchUiState>(SearchUiState.Idle)
val uiState: StateFlow<SearchUiState> = _uiState.asStateFlow()
fun onQueryChanged(query: String) {
_uiState.value = SearchUiState.Loading
viewModelScope.launch {
val results = repo.search(query)
// update {} applies the change atomically, safe under concurrent callers.
_uiState.update { SearchUiState.Success(results) }
}
}
}Một câu hỏi tiếp theo thường gặp: tại sao lại phơi bày asStateFlow() thay vì trường mutable? Nó trao cho UI một góc nhìn chỉ đọc để trạng thái chỉ có thể thay đổi thông qua ViewModel, giữ nguyên luồng dữ liệu một chiều. Hướng dẫn về StateFlow và SharedFlow của chính Android khuyến nghị đúng mẫu hình này.
SharedFlow vs StateFlow: chọn đúng loại cho sự kiện
Câu hỏi bẫy kinh điển là "liệu StateFlow có thể truyền tải các sự kiện một lần không?" Câu trả lời trung thực là không, không một cách an toàn. Vì StateFlow conflate và loại trùng, một sự kiện điều hướng có thể bị bỏ rơi khi cập nhật diễn ra nhanh, và nó phát lại khi có thay đổi cấu hình vì một collector mới sẽ phát lại giá trị được giữ lại. SharedFlow với replay = 0 khắc phục cả hai: không gì được giữ lại, và mỗi lần phát chỉ đến được những collector đang hoạt động tại thời điểm phát.
class CheckoutViewModel : ViewModel() {
// replay = 0: a late collector must NOT re-receive a past navigation event.
private val _events = MutableSharedFlow<CheckoutEvent>(replay = 0)
val events: SharedFlow<CheckoutEvent> = _events.asSharedFlow()
fun onPaymentConfirmed() {
viewModelScope.launch {
// emit() suspends if the buffer is full; tryEmit() is the non-suspending variant.
_events.emit(CheckoutEvent.NavigateToReceipt)
}
}
}Đối với các câu hỏi về kiến trúc, lập luận sâu hơn phía sau việc tách trạng thái khỏi sự kiện xuất hiện trong bài so sánh MVVM và MVI, nơi MVI xem sự kiện như một luồng tường minh thay vì trạng thái mutable.
Chuyển một cold Flow thành hot StateFlow bằng stateIn
Nhà tuyển dụng thường hỏi làm thế nào để biến một cold flow của repository thành trạng thái UI. Câu trả lời là stateIn (cho một giá trị được giữ lại duy nhất) hoặc shareIn (cho một phát quảng bá không có giá trị hiện tại). Tham số SharingStarted kiểm soát khi nào upstream hoạt động, và WhileSubscribed(5_000) là lựa chọn tiêu chuẩn vì nó giữ luồng sống qua một lần xoay màn hình mà không rò rỉ khi màn hình đóng.
val profile: StateFlow<Profile> = repo.profileStream()
.stateIn(
scope = viewModelScope,
// Keep the upstream alive 5s after the last collector leaves, surviving rotation.
started = SharingStarted.WhileSubscribed(5_000),
initialValue = Profile.EMPTY
)Sự khác biệt giữa stateIn và shareIn chính xác là sự khác biệt giữa StateFlow và SharedFlow: stateIn cần một initialValue và giữ trạng thái, trong khi shareIn nhận một số đếm replay và phát quảng bá. Việc ôn lại mô hình coroutine rộng hơn trong hướng dẫn làm chủ Kotlin coroutines này giúp kết nối các operator này với scope và việc hủy.
Sẵn sàng chinh phục phỏng vấn Android?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Thu thập flow an toàn theo vòng đời của Android
Một câu hỏi cấp senior là làm sao thu thập một flow mà không rò rỉ công việc khi màn hình đang ở nền. Việc thu thập bên trong một launch thông thường vẫn tiếp tục chạy khi UI đã dừng, gây lãng phí CPU và có nguy cơ crash. repeatOnLifecycle(STARTED) hủy việc thu thập khi STOP và khởi động lại khi START.
viewLifecycleOwner.lifecycleScope.launch {
// Collection restarts on STARTED and cancels on STOPPED: no wasted work in the background.
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state -> render(state) }
}
}Trong Jetpack Compose, tương đương là collectAsStateWithLifecycle(), nó dừng thu thập khi ứng dụng bị đưa xuống nền và tiếp tục khi quay lại.
@Composable
fun ProfileScreen(viewModel: ProfileViewModel) {
// collectAsStateWithLifecycle stops collecting when the app is backgrounded.
val state by viewModel.uiState.collectAsStateWithLifecycle()
ProfileContent(state)
}Nhận thức về vòng đời này là lý do StateFlow kết hợp với collectAsStateWithLifecycle là mẫu trạng thái mặc định trong các ứng dụng Compose hiện đại, một điểm được bàn thêm trong những câu hỏi phỏng vấn Jetpack Compose này.
Cách kiểm thử các lần phát của StateFlow và SharedFlow
Kiểm thử là nơi các ứng viên senior tạo khác biệt. Cách tiếp cận ngây thơ đọc stateFlow.value một lần, nhưng điều đó bỏ lỡ các trạng thái trung gian và hoàn toàn không thể quan sát một SharedFlow. Câu trả lời tiêu chuẩn nêu tên thư viện Turbine, nó tạm dừng cho đến khi mỗi lần phát đến và làm bài kiểm thử thất bại nếu một giá trị kỳ vọng không bao giờ xuất hiện.
@Test
fun `emits Loading then Success on query`() = runTest {
val viewModel = SearchViewModel(fakeRepo)
viewModel.uiState.test {
assertEquals(SearchUiState.Idle, awaitItem()) // initial value
viewModel.onQueryChanged("kotlin")
assertEquals(SearchUiState.Loading, awaitItem()) // intermediate state
assertEquals(SearchUiState.Success(fakeResults), awaitItem())
cancelAndIgnoreRemainingEvents()
}
}Kết hợp Turbine với runTest và một test dispatcher giữ cho các assertion mang tính xác định, và việc nhắc đến StandardTestDispatcher so với UnconfinedTestDispatcher cho thấy kinh nghiệm kiểm thử coroutine thực sự.
Những câu hỏi phụ thường gặp về Kotlin Flow
Nhà tuyển dụng kết thúc bằng những câu hỏi kiểm tra nhanh dồn dập. SharingStarted.WhileSubscribed(5_000) làm gì? Nó khởi động upstream khi có subscriber đầu tiên và dừng lại 5 giây sau khi subscriber cuối cùng hủy đăng ký, đủ lâu để sống sót qua một lần xoay màn hình. Tại sao StateFlow bỏ qua các giá trị trùng lặp? Nó so sánh bằng equals(), nên phát ra một giá trị bằng nhau là một thao tác vô nghĩa, và đó là lý do data class quan trọng đối với trạng thái. SharedFlow có thể hoạt động như StateFlow không? Đặt replay = 1 khiến nó giữ lại giá trị cuối cùng, nhưng nó vẫn thiếu một .value đồng bộ và không bao giờ loại trùng. Điều gì kiểm soát back-pressure trên một SharedFlow? Các tham số extraBufferCapacity và onBufferOverflow, với BufferOverflow.DROP_OLDEST là lựa chọn phổ biến cho sự kiện. Việc luyện tập sâu hơn với các operator này nằm trong mô-đun phỏng vấn về Kotlin coroutines và Flow.
Dùng StateFlow cho các sự kiện điều hướng hoặc snackbar. Vì nó phát lại giá trị cuối cùng cho các collector mới, sự kiện sẽ kích hoạt lại sau một lần xoay màn hình và người dùng bị điều hướng hai lần. Hãy chọn SharedFlow với replay = 0, hoặc mô hình hóa một sự kiện một lần được xóa sau khi xử lý.
Mã nguồn kotlinx.coroutines và tài liệu tham chiếu gói flow trên GitHub đáng để lướt qua trước một buổi phỏng vấn, vì KDoc trên StateFlow và SharedFlow nêu chính xác các đảm bảo về conflation và replay.
Kết luận
Sự phân biệt giữa Kotlin Flow vs StateFlow vs SharedFlow tưởng thưởng cho ngôn ngữ chính xác trong một buổi phỏng vấn. Những điểm chính cần mang theo:
Flowlà cold: producer chạy lại cho mỗi collector, nên nó phù hợp với các pipeline dữ liệu theo yêu cầu.StateFlowlà hot, luôn giữ một giá trị, đồng thời conflate và loại trùng, khiến nó trở thành công cụ cho trạng thái UI.SharedFlowlà hot không bắt buộc giá trị khởi tạo, khiến nó là lựa chọn đúng đắn cho các sự kiện một lần.- Chuyển cold thành hot bằng
stateInhoặcshareIn, và dùngSharingStarted.WhileSubscribed(5_000)để sống sót qua lần xoay màn hình. - Thu thập bằng
repeatOnLifecycle(STARTED)hoặccollectAsStateWithLifecycle()để tránh công việc nền và rò rỉ. - Không bao giờ truyền tải sự kiện qua StateFlow: hành vi replay của nó khiến chúng kích hoạt lại sau một thay đổi cấu hình.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

Kotlin 2.3 cho Android: Name-Based Destructuring, KMP và Câu Hỏi Phỏng Vấn 2026
Câu hỏi phỏng vấn Kotlin 2.3 dành cho lập trình viên Android năm 2026. Name-based destructuring, KMP, context parameters, Flow và coroutines kèm ví dụ code.

20 Câu Hỏi Phỏng Vấn Jetpack Compose Hàng Đầu Năm 2026
20 câu hỏi phỏng vấn Jetpack Compose thường gặp nhất: recomposition, quản lý state, navigation, hiệu năng và các pattern kiến trúc với ví dụ code chi tiết.

Kotlin Coroutines cho Android: Huong dan day du 2026
Huong dan toan dien ve Kotlin coroutines trong phat trien Android: suspend functions, scopes, dispatchers va cac pattern nang cao.