# 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. - Published: 2026-07-01 - Updated: 2026-07-07 - Author: SharpSkill - Tags: android, kotlin, coroutines, flow, interview - Reading time: 9 min --- 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. > **Câu trả lời trong 20 giây** > > 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](https://kotlinlang.org/docs/flow.html) 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. ```kotlin // SearchRepository.kt fun searchResults(query: String): Flow> = 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`. ```kotlin // SearchViewModel.kt 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.Idle) val uiState: StateFlow = _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](https://developer.android.com/kotlin/flow/stateflow-and-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. ```kotlin // CheckoutViewModel.kt class CheckoutViewModel : ViewModel() { // replay = 0: a late collector must NOT re-receive a past navigation event. private val _events = MutableSharedFlow(replay = 0) val events: SharedFlow = _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](/blog/android/mvvm-vs-mvi-architecture), 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. ```kotlin // ProfileViewModel.kt val profile: StateFlow = 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](/blog/android/mastering-kotlin-coroutines) giúp kết nối các operator này với scope và việc hủy. ## 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`. ```kotlin // ProfileFragment.kt (View system) 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. ```kotlin // ProfileScreen.kt (Jetpack Compose) @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](/blog/android/jetpack-compose-interview-questions). ## 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. ```kotlin // SearchViewModelTest.kt @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](/technologies/android/interview-questions/android-kotlin-coroutines-flow). > **Sai lầm khiến ứng viên trượt** > > 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](https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-core) đá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: - `Flow` là 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. - `StateFlow` là 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. - `SharedFlow` là 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 `stateIn` hoặc `shareIn`, và dùng `SharingStarted.WhileSubscribed(5_000)` để sống sót qua lần xoay màn hình. - Thu thập bằng `repeatOnLifecycle(STARTED)` hoặc `collectAsStateWithLifecycle()` để 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. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/vi/blog/android/kotlin-flow-vs-stateflow-vs-sharedflow-interview