> android-coroutines
Authoritative rules and patterns for production-quality Kotlin Coroutines onto Android. Covers structured concurrency, lifecycle integration, and reactive streams.
curl "https://skillshub.wtf/new-silvermoon/awesome-android-agent-skills/android-coroutines?format=md"Android Coroutines Expert Skill
This skill provides authoritative rules and patterns for writing production-quality Kotlin Coroutines code on Android. It enforces structured concurrency, lifecycle safety, and modern best practices (2025 standards).
Responsibilities
- Asynchronous Logic: Implementing suspend functions, Dispatcher management, and parallel execution.
- Reactive Streams: Implementing
Flow,StateFlow,SharedFlow, andcallbackFlow. - Lifecycle Integration: Managing scopes (
viewModelScope,lifecycleScope) and safe collection (repeatOnLifecycle). - Error Handling: Implementing
CoroutineExceptionHandler,SupervisorJob, and propertry-catchhierarchies. - Cancellability: Ensuring long-running operations are cooperative using
ensureActive(). - Testing: Setting up
TestDispatcherandrunTest.
Applicability
Activate this skill when the user asks to:
- "Fetch data from an API/Database."
- "Perform background processing."
- "Fix a memory leak" related to threads/tasks.
- "Convert a listener/callback to Coroutines."
- "Implement a ViewModel."
- "Handle UI state updates."
Critical Rules & Constraints
1. Dispatcher Injection (Testability)
- NEVER hardcode Dispatchers (e.g.,
Dispatchers.IO,Dispatchers.Default) inside classes. - ALWAYS inject a
CoroutineDispatchervia the constructor. - DEFAULT to
Dispatchers.IOin the constructor argument for convenience, but allow it to be overridden.
// CORRECT
class UserRepository(
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) { ... }
// INCORRECT
class UserRepository {
fun getData() = withContext(Dispatchers.IO) { ... }
}
2. Main-Safety
- All suspend functions defined in the Data or Domain layer must be main-safe.
- One-shot calls should be exposed as
suspendfunctions. - Data changes should be exposed as
Flow. - The caller (ViewModel) should be able to call them from
Dispatchers.Mainwithout blocking the UI. - Use
withContext(dispatcher)inside the repository implementation to move execution to the background.
3. Lifecycle-Aware Collection
- NEVER collect a flow directly in
lifecycleScope.launchorlaunchWhenStarted(deprecated/unsafe). - ALWAYS use
repeatOnLifecycle(Lifecycle.State.STARTED)for collecting flows in Activities or Fragments.
// CORRECT
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { ... }
}
}
4. ViewModel Scope Usage
- Use
viewModelScopefor initiating coroutines in ViewModels. - Do not expose suspend functions from the ViewModel to the View. The ViewModel should expose
StateFloworSharedFlowthat the View observes.
5. Mutable State Encapsulation
- NEVER expose
MutableStateFloworMutableSharedFlowpublicly. - Expose them as read-only
StateFloworFlowusing.asStateFlow()or upcasting.
6. GlobalScope Prohibition
- NEVER use
GlobalScope. It breaks structured concurrency and leads to leaks. - If a task must survive the current scope, use an injected
applicationScope(a custom scope tied to the Application lifecycle).
7. Exception Handling
- NEVER catch
CancellationExceptionin a genericcatch (e: Exception)block without rethrowing it. - Use
runCatchingonly if you explicitly rethrowCancellationException. - Use
CoroutineExceptionHandleronly for top-level coroutines (insidelaunch). It has no effect insideasyncor child coroutines.
8. Cancellability
- Coroutines feature cooperative cancellation. They don't stop immediately unless they check for cancellation.
- ALWAYS call
ensureActive()oryield()in tight loops (e.g., processing a large list, reading files) to check for cancellation. - Standard functions like
delay()andwithContext()are already cancellable.
9. Callback Conversion
- Use
callbackFlowto convert callback-based APIs to Flow. - ALWAYS use
awaitCloseat the end of thecallbackFlowblock to unregister listeners.
Code Patterns
Repository Pattern with Flow
class NewsRepository(
private val remoteDataSource: NewsRemoteDataSource,
private val externalScope: CoroutineScope, // For app-wide events
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
val newsUpdates: Flow<List<News>> = flow {
val news = remoteDataSource.fetchLatestNews()
emit(news)
}.flowOn(ioDispatcher) // Upstream executes on IO
}
Parallel Execution
suspend fun loadDashboardData() = coroutineScope {
val userDeferred = async { userRepo.getUser() }
val feedDeferred = async { feedRepo.getFeed() }
// Wait for both
DashboardData(
user = userDeferred.await(),
feed = feedDeferred.await()
)
}
Testing with runTest
@Test
fun testViewModel() = runTest {
val testDispatcher = StandardTestDispatcher(testScheduler)
val viewModel = MyViewModel(testDispatcher)
viewModel.loadData()
advanceUntilIdle() // Process coroutines
assertEquals(expectedState, viewModel.uiState.value)
}
> related_skills --same-repo
> xml-to-compose-migration
Convert Android XML layouts to Jetpack Compose. Use when asked to migrate Views to Compose, convert XML to Composables, or modernize UI from View system to Compose.
> rxjava-to-coroutines-migration
Guide and execute the migration of asynchronous code from RxJava to Kotlin Coroutines and Flow. Use this skill when a user asks to convert RxJava (Observables, Singles, Completables, Subjects) to Coroutines (suspend functions, Flows, StateFlows).
> kotlin-concurrency-expert
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.
> gradle-build-performance
Debug and optimize Android/Gradle build performance. Use when builds are slow, investigating CI/CD performance, analyzing build scans, or identifying compilation bottlenecks.