Bỏ qua để đến nội dung

Coroutines & Flow

Phần này tổng hợp các câu hỏi về Kotlin CoroutinesFlow - nền tảng async programming trong Android.


Trả lời:

Coroutine là lightweight concurrent unit có thể suspendresume 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 - blocks
Thread {
val result = networkCall() // Blocks this thread
runOnUiThread { updateUI(result) }
}.start()
// Coroutine - suspends
viewModelScope.launch {
val result = networkCall() // Suspends, doesn't block
updateUI(result) // Runs on Main after suspension
}
// Can launch millions of coroutines
repeat(100_000) {
launch {
delay(1000)
print(".")
}
}

📚 Tìm hiểu thêm: Coroutines trong Android


Trả lời:

suspend function là function có thể pauseresume execution mà không block thread.

// suspend function can only be called from coroutine or another suspend function
suspend fun fetchUser(id: String): User {
delay(1000) // Built-in suspend function
return api.getUser(id) // Another suspend function
}
// ❌ Cannot call from regular function
fun regularFunction() {
fetchUser("123") // Compile error!
}
// ✅ Must call from coroutine
viewModelScope.launch {
val user = fetchUser("123") // OK
}
// ✅ Or from another suspend function
suspend fun loadData() {
val user = fetchUser("123") // OK
}

Internal: Compiler transforms suspend functions to use Continuation (callbacks) internally.


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.launch mặc định dùng Dispatchers.Main.immediate


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 cleared
class UserViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
val data = repository.getData()
_state.value = data
}
}
}
// Activity/Fragment - auto-cancelled when lifecycle ends
class 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
}

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 all
val 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 isolated
val supervisorJob = SupervisorJob()
val scope = CoroutineScope(supervisorJob + Dispatchers.Default)
scope.launch { task1() } // Continues
scope.launch { throw Exception() } // Only this fails
scope.launch { task3() } // Continues
// supervisorScope block
viewModelScope.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

Trả lời:

Structured Concurrency đảm bảo:

  1. Parent waits for all children to complete
  2. Cancellation propagates to children
  3. 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 propagates
val job = viewModelScope.launch {
coroutineScope {
launch { longTask1() } // Cancelled
launch { longTask2() } // Cancelled
}
}
job.cancel() // Cancels all children

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 Concurrent
suspend 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)
}

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)
}
// Usage
viewModelScope.launch {
loadUser("123")
.onSuccess { _user.value = it }
.onFailure { _error.value = it.message }
}

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 pattern
viewModelScope.launch {
try {
longRunningTask()
} catch (e: Exception) {
if (e is CancellationException) throw e
handleError(e)
}
}
// Or use runCatching carefully
runCatching { suspendFunction() }
.onSuccess { }
.onFailure { if (it is CancellationException) throw it }

⚠️ Quan trọng: Luôn re-throw CancellationException để không chặn cancellation propagation!


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
// LiveData
class 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
@Composable
fun ItemList(viewModel: ViewModel) {
val items by viewModel.data.collectAsStateWithLifecycle()
LazyColumn {
items(items) { ItemRow(it) }
}
}

📚 Tìm hiểu thêm: Flow trong Android


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 events
LaunchedEffect(Unit) {
viewModel.events.collect { event ->
when (event) {
is Event.ShowSnackbar -> snackbarHostState.showSnackbar(event.text)
is Event.Navigate -> navController.navigate(event.route)
}
}
}

Trả lời:

// Transform
repository.getUsers()
.map { users -> users.filter { it.isActive } }
.filter { it.isNotEmpty() }
// Combine multiple flows
combine(userFlow, settingsFlow) { user, settings ->
ProfileState(user, settings)
}
// FlatMap
searchQuery
.debounce(300) // Wait 300ms after last change
.distinctUntilChanged() // Skip if same
.flatMapLatest { query -> // Cancel previous search
repository.search(query)
}
// Error handling
flow
.catch { e -> emit(emptyList()) } // Handle upstream errors
.onEach { result -> /* process */ }
.launchIn(viewModelScope)
// Retry
repository.fetchData()
.retry(3) { e -> e is IOException }
.catch { emit(cachedData) }

Trả lời:

Function Lifecycle-aware Use Case
collectAsState Quick testing
collectAsStateWithLifecycle Production
// ❌ Continues collecting in background
@Composable
fun BadScreen(viewModel: ViewModel) {
val state by viewModel.flow.collectAsState()
}
// ✅ Stops when lifecycle is less than STARTED
@Composable
fun GoodScreen(viewModel: ViewModel) {
val state by viewModel.flow.collectAsStateWithLifecycle()
}

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 rule
class MainDispatcherRule(
private val dispatcher: TestDispatcher = UnconfinedTestDispatcher()
) : TestWatcher() {
override fun starting(description: Description) {
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description) {
Dispatchers.resetMain()
}
}

// Scope
viewModelScope.launch { } // ViewModel
lifecycleScope.launch { } // Activity/Fragment
// Dispatchers
withContext(Dispatchers.IO) { } // I/O
withContext(Dispatchers.Default) { } // CPU
// Builders
launch { } // Fire-and-forget
async { }.await() // Get result
// Flow
flow.collect { } // Terminal operator
flow.collectAsStateWithLifecycle() // In Compose
flow.stateIn(scope, started, initial) // Convert to StateFlow
// Error handling
try { } catch (e: Exception) { if (e is CancellationException) throw e }
runCatching { }.onSuccess { }.onFailure { }

📚 Tìm hiểu thêm: Coroutines, Flow