kotlin-concurrency-expert

Kotlin Concurrency Expert

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "kotlin-concurrency-expert" with this command: npx skills add new-silvermoon/awesome-android-agent-skills/new-silvermoon-awesome-android-agent-skills-kotlin-concurrency-expert

Kotlin Concurrency Expert

Overview

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.

Workflow

  1. Triage the Issue
  • Capture the exact error, crash, or symptom (ANR, memory leak, race condition, incorrect state).

  • Check project coroutines setup: kotlinx-coroutines-android version, lifecycle-runtime-ktx version.

  • Identify the current scope context (viewModelScope , lifecycleScope , custom scope, or none).

  • Confirm whether the code is UI-bound (Dispatchers.Main ) or intended to run off the main thread (Dispatchers.IO , Dispatchers.Default ).

  • Verify Dispatcher injection patterns for testability.

  1. Apply the Smallest Safe Fix

Prefer edits that preserve existing behavior while satisfying structured concurrency and lifecycle safety.

Common fixes:

  • ANR / Main thread blocking: Move heavy work to withContext(Dispatchers.IO) or Dispatchers.Default ; ensure suspend functions are main-safe.

  • Memory leaks / zombie coroutines: Replace GlobalScope with a lifecycle-bound scope (viewModelScope , lifecycleScope , or injected applicationScope ).

  • Lifecycle collection issues: Replace deprecated launchWhenStarted with repeatOnLifecycle(Lifecycle.State.STARTED) .

  • State exposure: Encapsulate MutableStateFlow / MutableSharedFlow ; expose read-only StateFlow or Flow .

  • CancellationException swallowing: Ensure generic catch (e: Exception) blocks rethrow CancellationException .

  • Non-cooperative cancellation: Add ensureActive() or yield() in tight loops for cooperative cancellation.

  • Callback APIs: Convert listeners to callbackFlow with proper awaitClose cleanup.

  • Hardcoded Dispatchers: Inject CoroutineDispatcher via constructor for testability.

Critical Rules

Dispatcher Injection (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) { ... } }

Lifecycle-Aware Collection

// 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) } }

State Encapsulation

// 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 }

Exception Handling

// 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! }

Cooperative Cancellation

// 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 } }

Callback Conversion

// CORRECT: callbackFlow with awaitClose fun locationUpdates(): Flow<Location> = callbackFlow { val listener = LocationListener { location -> trySend(location) } locationManager.requestLocationUpdates(listener)

awaitClose { locationManager.removeUpdates(listener) }

}

Scope Guidelines

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

Testing Pattern

@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)

}

Reference Material

  • Kotlin Coroutines Best Practices

  • StateFlow and SharedFlow

  • repeatOnLifecycle API

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

gradle-build-performance

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

compose-ui

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

android-testing

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

android-gradle-logic

No summary provided by upstream source.

Repository SourceNeeds Review