android-kotlin-coroutines

安装量: 57
排名: #13100

安装

npx skills add https://github.com/thebushidocollective/han --skill android-kotlin-coroutines

Android - Kotlin Coroutines

Asynchronous programming patterns using Kotlin coroutines and Flow in Android.

Key Concepts Coroutine Basics // Launching coroutines class UserViewModel : ViewModel() {

fun loadUser(id: String) {
    // viewModelScope is automatically cancelled when ViewModel is cleared
    viewModelScope.launch {
        try {
            val user = userRepository.getUser(id)
            _uiState.value = UiState.Success(user)
        } catch (e: Exception) {
            _uiState.value = UiState.Error(e.message)
        }
    }
}

// For operations that return a value
fun fetchUserAsync(id: String): Deferred<User> {
    return viewModelScope.async {
        userRepository.getUser(id)
    }
}

}

// Suspend functions suspend fun fetchUserFromNetwork(id: String): User { return withContext(Dispatchers.IO) { api.getUser(id) } }

Dispatchers // Main - UI operations withContext(Dispatchers.Main) { textView.text = "Updated" }

// IO - Network, database, file operations withContext(Dispatchers.IO) { val data = api.fetchData() database.save(data) }

// Default - CPU-intensive work withContext(Dispatchers.Default) { val result = expensiveComputation(data) }

// Custom dispatcher for limited parallelism val limitedDispatcher = Dispatchers.IO.limitedParallelism(4)

Flow Basics // Creating flows fun getUsers(): Flow> = flow { while (true) { val users = api.getUsers() emit(users) delay(30_000) // Poll every 30 seconds } }

// Flow from Room @Dao interface UserDao { @Query("SELECT * FROM users") fun getAllUsers(): Flow> }

// Collecting flows viewModelScope.launch { userRepository.getUsers() .catch { e -> _uiState.value = UiState.Error(e) } .collect { users -> _uiState.value = UiState.Success(users) } }

StateFlow and SharedFlow class SearchViewModel : ViewModel() { // StateFlow - always has a current value private val _searchQuery = MutableStateFlow("") val searchQuery: StateFlow = _searchQuery.asStateFlow()

// SharedFlow - for events without initial value
private val _events = MutableSharedFlow<UiEvent>()
val events: SharedFlow<UiEvent> = _events.asSharedFlow()

// Derived state from flow
val searchResults: StateFlow<List<Item>> = _searchQuery
    .debounce(300)
    .filter { it.length >= 2 }
    .flatMapLatest { query ->
        searchRepository.search(query)
    }
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = emptyList()
    )

fun updateQuery(query: String) {
    _searchQuery.value = query
}

fun sendEvent(event: UiEvent) {
    viewModelScope.launch {
        _events.emit(event)
    }
}

}

Best Practices Structured Concurrency // Good: Using coroutineScope for parallel operations suspend fun loadDashboard(): Dashboard = coroutineScope { val userDeferred = async { userRepository.getUser() } val ordersDeferred = async { orderRepository.getOrders() } val notificationsDeferred = async { notificationRepository.getNotifications() }

// All complete or all fail together
Dashboard(
    user = userDeferred.await(),
    orders = ordersDeferred.await(),
    notifications = notificationsDeferred.await()
)

}

// With timeout suspend fun loadWithTimeout(): Data { return withTimeout(5000) { api.fetchData() } }

// Or with nullable result on timeout suspend fun loadWithTimeoutOrNull(): Data? { return withTimeoutOrNull(5000) { api.fetchData() } }

Exception Handling // Using runCatching suspend fun safeApiCall(): Result = runCatching { api.getUser() }

// Handling in ViewModel fun loadUser() { viewModelScope.launch { safeApiCall() .onSuccess { user -> _uiState.value = UiState.Success(user) } .onFailure { error -> _uiState.value = UiState.Error(error.message) } } }

// SupervisorJob for independent child failures class MyViewModel : ViewModel() { private val supervisorJob = SupervisorJob() private val scope = CoroutineScope(Dispatchers.Main + supervisorJob)

fun loadMultiple() {
    scope.launch {
        // This failure won't cancel other children
        userRepository.getUser()
    }
    scope.launch {
        // This continues even if above fails
        orderRepository.getOrders()
    }
}

}

Flow Operators // Transformation operators userRepository.getUsers() .map { users -> users.filter { it.isActive } } .distinctUntilChanged() .collect { activeUsers -> updateUI(activeUsers) }

// Combining flows val combined: Flow> = combine( userRepository.getUser(), settingsRepository.getSettings() ) { user, settings -> Pair(user, settings) }

// FlatMapLatest for search searchQuery .debounce(300) .flatMapLatest { query -> if (query.isEmpty()) flowOf(emptyList()) else searchRepository.search(query) } .collect { results -> updateResults(results) }

// Retry with exponential backoff api.fetchData() .retry(3) { cause -> if (cause is IOException) { delay(1000 * (2.0.pow(retryCount)).toLong()) true } else false }

Lifecycle-Aware Collection // In Compose - collectAsStateWithLifecycle @Composable fun UserScreen(viewModel: UserViewModel = hiltViewModel()) { val uiState by viewModel.uiState.collectAsStateWithLifecycle()

UserContent(uiState)

}

// In Activity/Fragment - repeatOnLifecycle class UserFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> updateUI(state) } } } } }

// Multiple flows viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { launch { viewModel.users.collect { updateUserList(it) } } launch { viewModel.events.collect { handleEvent(it) } } } }

Common Patterns Repository Pattern with Flow class UserRepository( private val api: UserApi, private val dao: UserDao, private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) { fun getUser(id: String): Flow = flow { // Emit cached data first dao.getUser(id)?.let { emit(it.toDomain()) }

    // Fetch from network
    val networkUser = api.getUser(id)
    dao.insertUser(networkUser.toEntity())
    emit(networkUser.toDomain())
}
.flowOn(dispatcher)
.catch { e ->
    // Log error, emit from cache if available
    dao.getUser(id)?.let { emit(it.toDomain()) }
        ?: throw e
}

suspend fun refreshUsers() {
    withContext(dispatcher) {
        val users = api.getUsers()
        dao.deleteAll()
        dao.insertAll(users.map { it.toEntity() })
    }
}

}

Cancellation Handling suspend fun downloadFile(url: String): ByteArray { return withContext(Dispatchers.IO) { val connection = URL(url).openConnection() connection.inputStream.use { input -> val buffer = ByteArrayOutputStream() val data = ByteArray(4096)

        while (true) {
            // Check for cancellation
            ensureActive()

            val count = input.read(data)
            if (count == -1) break
            buffer.write(data, 0, count)
        }

        buffer.toByteArray()
    }
}

}

// Cancellable flow fun pollData(): Flow = flow { while (currentCoroutineContext().isActive) { emit(api.fetchData()) delay(5000) } }

Debounce and Throttle // Debounce - wait for pause in emissions @Composable fun SearchField(onSearch: (String) -> Unit) { var query by remember { mutableStateOf("") }

LaunchedEffect(query) {
    delay(300) // Debounce
    if (query.isNotEmpty()) {
        onSearch(query)
    }
}

TextField(value = query, onValueChange = { query = it })

}

// In ViewModel private val _searchQuery = MutableStateFlow("")

val searchResults = _searchQuery .debounce(300) .distinctUntilChanged() .flatMapLatest { query -> searchRepository.search(query) } .stateIn(viewModelScope, SharingStarted.Lazily, emptyList())

Anti-Patterns GlobalScope Usage

Bad:

GlobalScope.launch { // Never cancelled, leaks memory fetchData() }

Good:

viewModelScope.launch { // Properly scoped fetchData() }

Blocking Calls on Main Thread

Bad:

fun loadData() { runBlocking { // Blocks main thread! api.fetchData() } }

Good:

fun loadData() { viewModelScope.launch { withContext(Dispatchers.IO) { api.fetchData() } } }

Flow Collection Without Lifecycle

Bad:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) { lifecycleScope.launch { viewModel.uiState.collect { // Collects even when in background updateUI(it) } } }

Good:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { updateUI(it) } } } }

Creating New Flow on Each Call

Bad:

// Creates new flow each time fun getUsers(): Flow> = userDao.getAllUsers()

// Called multiple times, multiple database subscriptions

Good:

// Shared flow, single subscription val users: StateFlow> = userDao.getAllUsers() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

Related Skills android-jetpack-compose: UI integration with coroutines android-architecture: Architectural patterns using coroutines

返回排行榜