android-coroutines

Implémenter et réviser les coroutines Kotlin sur Android : configurer les CoroutineScopes, gérer les Dispatchers, exposer StateFlow/SharedFlow depuis les ViewModels, collecter avec…

npx skills add https://github.com/livekit/client-sdk-android --skill android-coroutines

Android Coroutines Expert Skill

Workflow

  1. Identify scope — Determine the correct CoroutineScope (viewModelScope, lifecycleScope, or injected applicationScope).
  2. Wire data layer — Expose data as suspend functions (one-shot) or Flow (streams) with injected Dispatchers.
  3. Connect UI — Collect flows using repeatOnLifecycle(Lifecycle.State.STARTED) and expose read-only StateFlow.
  4. Verify — Run ./gradlew test and confirm coroutine behavior. If tests fail, check: uncaught CancellationException in generic catch blocks, GlobalScope usage causing leaked coroutines, missing awaitClose in callbackFlow, or hardcoded Dispatchers breaking test determinism. Run ./gradlew detektDebug to catch structural issues.

Critical Rules

1. Dispatcher Injection (Testability)

  • NEVER hardcode Dispatchers (e.g., Dispatchers.IO, Dispatchers.Default) inside classes.
  • ALWAYS inject a CoroutineDispatcher via the constructor.
  • DEFAULT to 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) { ... }
}

2. Main-Safety

  • All suspend functions in Data/Domain layers must be main-safe — use withContext(dispatcher) internally.
  • One-shot calls: expose as suspend functions. Data streams: expose as Flow.

3. Lifecycle-Aware Collection

  • NEVER collect a flow directly in lifecycleScope.launch or launchWhenStarted (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 viewModelScope for initiating coroutines in ViewModels.
  • Do not expose suspend functions from the ViewModel to the View. The ViewModel should expose StateFlow or SharedFlow that the View observes.

5. Mutable State Encapsulation

  • NEVER expose MutableStateFlow or MutableSharedFlow publicly.
  • Expose them as read-only StateFlow or Flow using .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 CancellationException in a generic catch (e: Exception) block without rethrowing it.
  • Use runCatching only if you explicitly rethrow CancellationException.
  • Use CoroutineExceptionHandler only for top-level coroutines (inside launch). It has no effect inside async or child coroutines.

8. Cancellability

  • ALWAYS call ensureActive() or yield() in tight loops (e.g., processing a large list, reading files).
  • delay() and withContext() are already cancellable — no extra checks needed there.

9. Callback Conversion

  • Use callbackFlow to convert callback-based APIs to Flow.
  • ALWAYS use awaitClose at the end of the callbackFlow block 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)
}

Plus de skills de livekit

livekit-agents
livekit
We need to translate the given English text into French, preserving the name "livekit-agents" if it appears. The text is a description of an agent skill. The instruction says to translate only the text inside <text>, and not include the name unless it appears in the source text. The name "livekit-agents" is not in the provided text, so we don't include it. We must preserve product names, protocol names, URLs, numbers, technical terms. So "LiveKit Cloud", "LiveKit Inference", "API keys" should remain as is or translated? "LiveKit Cloud" is a product name, so keep as is. "LiveKit Inference" also product name. "API keys" is a technical term, but in French it's often "clés API" but the instruction says preserve technical terms? It says "preserve product names, protocol names, URLs, numbers, and technical terms." So we should keep "API keys" as is? Or translate? Typically "API keys" is a technical term that might be kept in English in
official
livekit-simulations
livekit
Générer des scénarios de test ciblés pour un agent vocal ou de chat LiveKit et les exécuter sous forme de simulations — localement, à partir du code propre de l'agent et de ce que l'utilisateur souhaite…
official
android-gradle-logic
livekit
Conseils d'expert pour configurer une logique de build Gradle évolutive à l'aide de plugins de convention et de catalogues de versions.
official
gradle-build-performance
livekit
Déboguer et optimiser les performances de construction Android/Gradle. À utiliser lorsque les builds sont lents, pour enquêter sur les performances CI/CD, analyser les scans de build, ou identifier…
official