Coroutines & Flow
Phần này tổng hợp các câu hỏi về Kotlin Coroutines và Flow - nền tảng async programming trong Android.
1. Coroutines Basics
Phần tiêu đề “1. Coroutines Basics”Q: Coroutine là gì? Khác gì Thread?
Phần tiêu đề “Q: Coroutine là gì? Khác gì Thread?”Trả lời:
Coroutine là lightweight concurrent unit có thể suspend và resume mà không block thread.
| Aspect | Thread | Coroutine |
|---|---|---|
| Weight | Heavy (~1MB stack) | Lightweight (~few KB) |
| Context Switch | OS-level, expensive | User-level, cheap |
| Blocking | Blocks thread | Suspends, frees thread |
| Quantity | Limited (hundreds) | Many (millions) |
| Creation | Expensive | Cheap |
// Thread - blocksThread { val result = networkCall() // Blocks this thread runOnUiThread { updateUI(result) }}.start()
// Coroutine - suspendsviewModelScope.launch { val result = networkCall() // Suspends, doesn't block updateUI(result) // Runs on Main after suspension}
// Can launch millions of coroutinesrepeat(100_000) { launch { delay(1000) print(".") }}📚 Tìm hiểu thêm: Coroutines trong Android
Q: suspend function là gì?
Phần tiêu đề “Q: suspend function là gì?”Trả lời:
suspend function là function có thể pause và resume execution mà không block thread.
// suspend function can only be called from coroutine or another suspend functionsuspend fun fetchUser(id: String): User { delay(1000) // Built-in suspend function return api.getUser(id) // Another suspend function}
// ❌ Cannot call from regular functionfun regularFunction() { fetchUser("123") // Compile error!}
// ✅ Must call from coroutineviewModelScope.launch { val user = fetchUser("123") // OK}
// ✅ Or from another suspend functionsuspend fun loadData() { val user = fetchUser("123") // OK}Internal: Compiler transforms suspend functions to use Continuation (callbacks) internally.
Q: Dispatchers là gì? Có bao nhiêu loại?
Phần tiêu đề “Q: Dispatchers là gì? Có bao nhiêu loại?”Trả lời:
Dispatcher xác định thread nào coroutine chạy trên.
| Dispatcher | Thread Pool | Use Case |
|---|---|---|
Dispatchers.Main |
Main/UI thread | UI updates |
Dispatchers.IO |
Shared pool (64+ threads) | Network, disk I/O |
Dispatchers.Default |
CPU cores | CPU-intensive work |
Dispatchers.Unconfined |
Current thread | Testing, special cases |
viewModelScope.launch(Dispatchers.Main) { showLoading()
val data = withContext(Dispatchers.IO) { api.fetchData() // Runs on IO thread }
val processed = withContext(Dispatchers.Default) { heavyComputation(data) // Runs on Default }
hideLoading() updateUI(processed) // Runs on Main}💡 Mẹo:
viewModelScope.launchmặc định dùngDispatchers.Main.immediate
2. Coroutine Scope
Phần tiêu đề “2. Coroutine Scope”Q: Các loại CoroutineScope trong Android?
Phần tiêu đề “Q: Các loại CoroutineScope trong Android?”Trả lời:
| Scope | Lifecycle | Use Case |
|---|---|---|
viewModelScope |
ViewModel | UI-related async work |
lifecycleScope |
Activity/Fragment | Lifecycle-aware work |
GlobalScope |
App process | ❌ Avoid! No lifecycle |
CoroutineScope() |
Custom | Custom lifecycle management |
// ViewModel - auto-cancelled when ViewModel clearedclass UserViewModel : ViewModel() { fun loadData() { viewModelScope.launch { val data = repository.getData() _state.value = data } }}
// Activity/Fragment - auto-cancelled when lifecycle endsclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
lifecycleScope.launch { // Cancelled when Activity destroyed repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.data.collect { updateUI(it) } } } }}
// ❌ GlobalScope - never use!GlobalScope.launch { // Never cancelled, can cause memory leaks}Q: Job và SupervisorJob khác nhau thế nào?
Phần tiêu đề “Q: Job và SupervisorJob khác nhau thế nào?”Trả lời:
| Type | Child Failure | Parent Failure |
|---|---|---|
| Job | Cancels parent + siblings | Cancels children |
| SupervisorJob | Doesn’t affect siblings | Cancels children |
// Job - one failure cancels allval job = Job()val scope = CoroutineScope(job + Dispatchers.Default)
scope.launch { task1() }scope.launch { throw Exception() } // Cancels task1 too!scope.launch { task3() } // Also cancelled
// SupervisorJob - failures isolatedval supervisorJob = SupervisorJob()val scope = CoroutineScope(supervisorJob + Dispatchers.Default)
scope.launch { task1() } // Continuesscope.launch { throw Exception() } // Only this failsscope.launch { task3() } // Continues
// supervisorScope blockviewModelScope.launch { supervisorScope { launch { task1() } // Independent launch { throw Exception() } // Fails alone launch { task3() } // Continues }}Use SupervisorJob when:
- Multiple independent tasks
- One failure shouldn’t affect others
- viewModelScope uses SupervisorJob by default
3. Structured Concurrency
Phần tiêu đề “3. Structured Concurrency”Q: Structured Concurrency là gì?
Phần tiêu đề “Q: Structured Concurrency là gì?”Trả lời:
Structured Concurrency đảm bảo:
- Parent waits for all children to complete
- Cancellation propagates to children
- Errors propagate to parent
suspend fun loadDashboard(): Dashboard = coroutineScope { // Both run concurrently val user = async { api.getUser() } val posts = async { api.getPosts() }
// Parent waits for both Dashboard(user.await(), posts.await())} // This line only reached when both complete
// Cancellation propagatesval job = viewModelScope.launch { coroutineScope { launch { longTask1() } // Cancelled launch { longTask2() } // Cancelled }}job.cancel() // Cancels all childrenQ: launch vs async?
Phần tiêu đề “Q: launch vs async?”Trả lời:
| Builder | Return | Use Case |
|---|---|---|
launch |
Job | Fire-and-forget |
async |
Deferred<T> |
Need result |
viewModelScope.launch { // launch - fire and forget launch { analytics.trackEvent() }
// async - concurrent with result val user = async { api.getUser() } val posts = async { api.getPosts() }
// Await results val data = CombinedData(user.await(), posts.await()) updateUI(data)}
// Sequential vs Concurrentsuspend fun sequential() { val a = api.callA() // Wait val b = api.callB() // Then this // Total: timeA + timeB}
suspend fun concurrent() = coroutineScope { val a = async { api.callA() } // Start val b = async { api.callB() } // Start immediately a.await() to b.await() // Total: max(timeA, timeB)}4. Error Handling
Phần tiêu đề “4. Error Handling”Q: Xử lý exceptions trong Coroutines?
Phần tiêu đề “Q: Xử lý exceptions trong Coroutines?”Trả lời:
1. try-catch in suspend function:
viewModelScope.launch { try { val data = api.fetchData() _state.value = UiState.Success(data) } catch (e: Exception) { _state.value = UiState.Error(e.message) }}2. CoroutineExceptionHandler:
val handler = CoroutineExceptionHandler { _, exception -> Log.e("Error", "Caught: ${exception.message}") _state.value = UiState.Error(exception.message)}
viewModelScope.launch(handler) { api.fetchData() // Exception handled by handler}3. supervisorScope for independent tasks:
supervisorScope { launch(handler) { riskyTask1() } // Fails independently launch(handler) { riskyTask2() } // Continues}4. runCatching for Result:
suspend fun loadUser(id: String): Result<User> = runCatching { api.getUser(id)}
// UsageviewModelScope.launch { loadUser("123") .onSuccess { _user.value = it } .onFailure { _error.value = it.message }}Q: CancellationException là gì?
Phần tiêu đề “Q: CancellationException là gì?”Trả lời:
CancellationException là special exception cho coroutine cancellation - không phải lỗi.
viewModelScope.launch { try { longRunningTask() } catch (e: CancellationException) { // ❌ Don't catch! Let it propagate throw e } catch (e: Exception) { // Handle other errors }}
// ✅ Better patternviewModelScope.launch { try { longRunningTask() } catch (e: Exception) { if (e is CancellationException) throw e handleError(e) }}
// Or use runCatching carefullyrunCatching { suspendFunction() } .onSuccess { } .onFailure { if (it is CancellationException) throw it }⚠️ Quan trọng: Luôn re-throw
CancellationExceptionđể không chặn cancellation propagation!
5. Flow
Phần tiêu đề “5. Flow”Q: Flow là gì? Khác gì LiveData?
Phần tiêu đề “Q: Flow là gì? Khác gì LiveData?”Trả lời:
Flow là cold asynchronous stream cho Kotlin, phù hợp cho reactive programming.
| Aspect | LiveData | Flow |
|---|---|---|
| Origins | Android | Kotlin (multiplatform) |
| Lifecycle | Built-in | Manual with operators |
| Operators | Limited | Rich (map, filter, combine) |
| Cold/Hot | Hot | Cold by default |
| Testing | Harder | Easier |
// LiveDataclass ViewModel : ViewModel() { private val _data = MutableLiveData<List<Item>>() val data: LiveData<List<Item>> = _data}
// Flow (Recommended)class ViewModel : ViewModel() { val data: StateFlow<List<Item>> = repository.getItems() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())}
// Collect in Compose@Composablefun ItemList(viewModel: ViewModel) { val items by viewModel.data.collectAsStateWithLifecycle()
LazyColumn { items(items) { ItemRow(it) } }}📚 Tìm hiểu thêm: Flow trong Android
Q: StateFlow vs SharedFlow?
Phần tiêu đề “Q: StateFlow vs SharedFlow?”Trả lời:
| Aspect | StateFlow | SharedFlow |
|---|---|---|
| Initial Value | Required | Optional |
| Replays | Latest value (1) | Configurable |
| Equality | Skips duplicate | Doesn’t skip |
| Use Case | UI state | Events |
class ViewModel : ViewModel() { // StateFlow - for UI state private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// SharedFlow - for one-time events private val _events = MutableSharedFlow<Event>() val events: SharedFlow<Event> = _events.asSharedFlow()
fun showMessage(text: String) { viewModelScope.launch { _events.emit(Event.ShowSnackbar(text)) } }}
// Collect eventsLaunchedEffect(Unit) { viewModel.events.collect { event -> when (event) { is Event.ShowSnackbar -> snackbarHostState.showSnackbar(event.text) is Event.Navigate -> navController.navigate(event.route) } }}Q: Flow operators thường dùng?
Phần tiêu đề “Q: Flow operators thường dùng?”Trả lời:
// Transformrepository.getUsers() .map { users -> users.filter { it.isActive } } .filter { it.isNotEmpty() }
// Combine multiple flowscombine(userFlow, settingsFlow) { user, settings -> ProfileState(user, settings)}
// FlatMapsearchQuery .debounce(300) // Wait 300ms after last change .distinctUntilChanged() // Skip if same .flatMapLatest { query -> // Cancel previous search repository.search(query) }
// Error handlingflow .catch { e -> emit(emptyList()) } // Handle upstream errors .onEach { result -> /* process */ } .launchIn(viewModelScope)
// Retryrepository.fetchData() .retry(3) { e -> e is IOException } .catch { emit(cachedData) }6. Best Practices
Phần tiêu đề “6. Best Practices”Q: collectAsStateWithLifecycle vs collectAsState?
Phần tiêu đề “Q: collectAsStateWithLifecycle vs collectAsState?”Trả lời:
| Function | Lifecycle-aware | Use Case |
|---|---|---|
collectAsState |
❌ | Quick testing |
collectAsStateWithLifecycle |
✅ | Production |
// ❌ Continues collecting in background@Composablefun BadScreen(viewModel: ViewModel) { val state by viewModel.flow.collectAsState()}
// ✅ Stops when lifecycle is less than STARTED@Composablefun GoodScreen(viewModel: ViewModel) { val state by viewModel.flow.collectAsStateWithLifecycle()}Q: Testing Coroutines?
Phần tiêu đề “Q: Testing Coroutines?”Trả lời:
@OptIn(ExperimentalCoroutinesApi::class)class ViewModelTest {
@get:Rule val mainDispatcherRule = MainDispatcherRule()
@Test fun `load data success`() = runTest { // Given val repository = FakeRepository() val viewModel = UserViewModel(repository)
// When viewModel.loadData()
// Then assertEquals(UiState.Success(data), viewModel.state.value) }}
// Main dispatcher ruleclass MainDispatcherRule( private val dispatcher: TestDispatcher = UnconfinedTestDispatcher()) : TestWatcher() {
override fun starting(description: Description) { Dispatchers.setMain(dispatcher) }
override fun finished(description: Description) { Dispatchers.resetMain() }}📝 Quick Reference
Phần tiêu đề “📝 Quick Reference”// ScopeviewModelScope.launch { } // ViewModellifecycleScope.launch { } // Activity/Fragment
// DispatcherswithContext(Dispatchers.IO) { } // I/OwithContext(Dispatchers.Default) { } // CPU
// Builderslaunch { } // Fire-and-forgetasync { }.await() // Get result
// Flowflow.collect { } // Terminal operatorflow.collectAsStateWithLifecycle() // In Composeflow.stateIn(scope, started, initial) // Convert to StateFlow
// Error handlingtry { } catch (e: Exception) { if (e is CancellationException) throw e }runCatching { }.onSuccess { }.onFailure { }📚 Tìm hiểu thêm: Coroutines, Flow