Authoritative rules and patterns for production-quality Kotlin Coroutines onto Android. Covers structured concurrency, lifecycle integration, and reactive streams.
59
48%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./.github/skills/android-coroutines/SKILL.mdThis 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).
Flow, StateFlow, SharedFlow, and callbackFlow.viewModelScope, lifecycleScope) and safe collection (repeatOnLifecycle).CoroutineExceptionHandler, SupervisorJob, and proper try-catch hierarchies.ensureActive().TestDispatcher and runTest.Activate this skill when the user asks to:
Dispatchers.IO, Dispatchers.Default) inside classes.CoroutineDispatcher via the constructor.Dispatchers.IO in 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) { ... }
}suspend functions.Flow.Dispatchers.Main without blocking the UI.withContext(dispatcher) inside the repository implementation to move execution to the background.lifecycleScope.launch or launchWhenStarted (deprecated/unsafe).repeatOnLifecycle(Lifecycle.State.STARTED) for collecting flows in Activities or Fragments.// CORRECT
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { ... }
}
}viewModelScope for initiating coroutines in ViewModels.StateFlow or SharedFlow that the View observes.MutableStateFlow or MutableSharedFlow publicly.StateFlow or Flow using .asStateFlow() or upcasting.GlobalScope. It breaks structured concurrency and leads to leaks.applicationScope (a custom scope tied to the Application lifecycle).CancellationException in a generic catch (e: Exception) block without rethrowing it.runCatching only if you explicitly rethrow CancellationException.CoroutineExceptionHandler only for top-level coroutines (inside launch). It has no effect inside async or child coroutines.ensureActive() or yield() in tight loops (e.g., processing a large list, reading files) to check for cancellation.delay() and withContext() are already cancellable.callbackFlow to convert callback-based APIs to Flow.awaitClose at the end of the callbackFlow block to unregister listeners.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
}suspend fun loadDashboardData() = coroutineScope {
val userDeferred = async { userRepo.getUser() }
val feedDeferred = async { feedRepo.getFeed() }
// Wait for both
DashboardData(
user = userDeferred.await(),
feed = feedDeferred.await()
)
}@Test
fun testViewModel() = runTest {
val testDispatcher = StandardTestDispatcher(testScheduler)
val viewModel = MyViewModel(testDispatcher)
viewModel.loadData()
advanceUntilIdle() // Process coroutines
assertEquals(expectedState, 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.