Kotlin Coroutines review and remediation for Android. Use when asked to review concurrency usage, fix coroutine-related bugs, improve thread safety, or resolve lifecycle issues in Kotlin/Android code.
96
95%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Review and fix Kotlin Coroutines issues in Android codebases by applying structured concurrency, lifecycle safety, proper scoping, and modern best practices with minimal behavior changes.
kotlinx-coroutines-android version, lifecycle-runtime-ktx version.viewModelScope, lifecycleScope, custom scope, or none).Dispatchers.Main) or intended to run off the main thread (Dispatchers.IO, Dispatchers.Default).Prefer edits that preserve existing behavior while satisfying structured concurrency and lifecycle safety.
Common fixes:
withContext(Dispatchers.IO) or Dispatchers.Default; ensure suspend functions are main-safe.GlobalScope with a lifecycle-bound scope (viewModelScope, lifecycleScope, or injected applicationScope).launchWhenStarted with repeatOnLifecycle(Lifecycle.State.STARTED).MutableStateFlow / MutableSharedFlow; expose read-only StateFlow or Flow.catch (e: Exception) blocks rethrow CancellationException.ensureActive() or yield() in tight loops for cooperative cancellation.callbackFlow with proper awaitClose cleanup.CoroutineDispatcher via constructor for testability.// CORRECT: Inject dispatcher
class UserRepository(
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
suspend fun fetchUser() = withContext(ioDispatcher) { ... }
}
// INCORRECT: Hardcoded dispatcher
class UserRepository {
suspend fun fetchUser() = withContext(Dispatchers.IO) { ... }
}// CORRECT: Use repeatOnLifecycle
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state -> updateUI(state) }
}
}
// INCORRECT: Direct collection (unsafe, deprecated)
lifecycleScope.launchWhenStarted {
viewModel.uiState.collect { state -> updateUI(state) }
}// CORRECT: Expose read-only StateFlow
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
}
// INCORRECT: Exposed mutable state
class MyViewModel : ViewModel() {
val uiState = MutableStateFlow(UiState()) // Leaks mutability
}// CORRECT: Rethrow CancellationException
try {
doSuspendWork()
} catch (e: CancellationException) {
throw e // Must rethrow!
} catch (e: Exception) {
handleError(e)
}
// INCORRECT: Swallows cancellation
try {
doSuspendWork()
} catch (e: Exception) {
handleError(e) // CancellationException swallowed!
}// CORRECT: Check for cancellation in tight loops
suspend fun processLargeList(items: List<Item>) {
items.forEach { item ->
ensureActive() // Check cancellation
processItem(item)
}
}
// INCORRECT: Non-cooperative (ignores cancellation)
suspend fun processLargeList(items: List<Item>) {
items.forEach { item ->
processItem(item) // Never checks cancellation
}
}// CORRECT: callbackFlow with awaitClose
fun locationUpdates(): Flow<Location> = callbackFlow {
val listener = LocationListener { location ->
trySend(location)
}
locationManager.requestLocationUpdates(listener)
awaitClose { locationManager.removeUpdates(listener) }
}| Scope | Use When | Lifecycle |
|---|---|---|
viewModelScope | ViewModel operations | Cleared with ViewModel |
lifecycleScope | UI operations in Activity/Fragment | Destroyed with lifecycle owner |
repeatOnLifecycle | Flow collection in UI | Started/Stopped with lifecycle state |
applicationScope (injected) | App-wide background work | Application lifetime |
GlobalScope | NEVER USE | Breaks structured concurrency |
@Test
fun `loading data updates state`() = runTest {
val testDispatcher = StandardTestDispatcher(testScheduler)
val repository = FakeRepository()
val viewModel = MyViewModel(repository, testDispatcher)
viewModel.loadData()
advanceUntilIdle()
assertEquals(UiState.Success(data), viewModel.uiState.value)
}3f68e39
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.