Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component...
npx skills add HuxleyMc/Android-Skills --skill "kotlin-coroutines-android"
Install specific skill from multi-skill repository
# Description
Guides implementation of Kotlin coroutines for Android asynchronous programming. Use when writing async code, handling background operations, implementing ViewModels with suspend functions, or converting callbacks to coroutines. Covers structured concurrency, main-safety, Flow, and Jetpack integration.
# SKILL.md
name: kotlin-coroutines-android
description: Guides implementation of Kotlin coroutines for Android asynchronous programming. Use when writing async code, handling background operations, implementing ViewModels with suspend functions, or converting callbacks to coroutines. Covers structured concurrency, main-safety, Flow, and Jetpack integration.
tags: ["kotlin", "coroutines", "android", "async", "viewmodel", "flow"]
difficulty: intermediate
category: architecture
version: "1.0.0"
last_updated: "2025-01-29"
Kotlin Coroutines for Android
Quick Start
Add dependencies to build.gradle:
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
}
Core Patterns
Basic Coroutine Launch
Launch (fire-and-forget):
viewModelScope.launch {
repository.saveData(data)
}
Async/await (return value):
viewModelScope.launch {
val user = async { repository.fetchUser(id) }.await()
updateUI(user)
}
Dispatchers
Use Dispatchers.IO for network/database, Dispatchers.Default for computation:
suspend fun fetchUser(id: String): User = withContext(Dispatchers.IO) {
api.getUser(id) // Main-safe function
}
// Usage: Called from any dispatcher
viewModelScope.launch {
val user = fetchUser(id) // Suspends, doesn't block
updateUI(user) // Back on Main
}
Scopes
ViewModel scope (auto-cancels on clear):
class MyViewModel : ViewModel() {
fun load() {
viewModelScope.launch {
// Work here
}
}
}
Lifecycle scope (Activity/Fragment):
class MyActivity : AppCompatActivity() {
fun load() {
lifecycleScope.launch {
// Work here
}
}
}
Common Patterns
Repository Pattern
class UserRepository(private val api: UserApi, private val dao: UserDao) {
suspend fun getUser(id: String): User = withContext(Dispatchers.IO) {
dao.getUser(id) ?: api.fetchUser(id).also { dao.insert(it) }
}
fun getAllUsers(): Flow<List<User>> = dao.getAllUsers()
.flowOn(Dispatchers.IO)
}
Parallel Decomposition
suspend fun loadDashboard(): Dashboard = coroutineScope {
val user = async { fetchUser() }
val orders = async { fetchOrders() }
val notifications = async { fetchNotifications() }
Dashboard(user.await(), orders.await(), notifications.await())
}
Error Handling
viewModelScope.launch {
val result = try {
repository.fetchData()
} catch (e: IOException) {
Result.Error("Network failed")
}
when (result) {
is Result.Success -> display(result.data)
is Result.Error -> showError(result.message)
}
}
Flow + StateFlow
// Repository
fun getUsers(): Flow<List<User>> = flow {
while (true) {
emit(api.fetchUsers())
delay(5000)
}
}.flowOn(Dispatchers.IO)
// ViewModel
val users: StateFlow<List<User>> = repository.getUsers()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
// UI
lifecycleScope.launch {
viewModel.users.collect { adapter.submitList(it) }
}
Cancellation
viewModelScope.launch {
try {
while (isActive) { // Check cancellation
doWork()
}
} catch (e: CancellationException) {
throw e // Re-throw to propagate
} finally {
closeResources() // Always cleanup
}
}
Timeout
// Throw on timeout
val result = withTimeout(5000) { fetchData() }
// Null on timeout
val result = withTimeoutOrNull(5000) { fetchData() }
Examples (Input → Output)
ViewModel with Loading State
Input request: "Create a ViewModel that loads news with loading state"
Output:
class NewsViewModel(private val repo: NewsRepository) : ViewModel() {
private val _news = MutableStateFlow<List<Article>>(emptyList())
val news: StateFlow<List<Article>> = _news.asStateFlow()
private val _loading = MutableStateFlow(false)
val loading: StateFlow<Boolean> = _loading.asStateFlow()
fun load() {
viewModelScope.launch {
_loading.value = true
try {
_news.value = repo.fetchNews()
} catch (e: Exception) {
// Handle error
} finally {
_loading.value = false
}
}
}
}
Callback to Coroutine Conversion
Input request: "Convert this callback-based API to suspend function"
Given callback API:
fun fetchUser(id: String, callback: (Result<User>) -> Unit)
Output:
suspend fun fetchUser(id: String): User = suspendCancellableCoroutine { continuation ->
fetchUser(id) { result ->
when (result) {
is Result.Success -> continuation.resume(result.data)
is Result.Error -> continuation.resumeWithException(result.exception)
}
}
continuation.invokeOnCancellation { cancelRequest(id) }
}
Best Practices
- Use structured concurrency: Always launch in
viewModelScopeorlifecycleScope - Create main-safe functions: Wrap blocking calls in
withContext(Dispatchers.IO) - Handle exceptions: Use try-catch in coroutines
- Respect cancellation: Check
isActivein loops, clean up infinally - Use appropriate dispatchers: Main for UI, IO for network/database, Default for CPU
- Use Flow for streams: Replace callbacks with Flow for reactive data
- Never use
runBlockingin production (tests only)
Resources
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.