正在加载,请稍候…

Kotlin Coroutines and Flow: Async Programming for Android and Backend

Master Kotlin Coroutines with suspend functions, structured concurrency, Flow for reactive streams, StateFlow, SharedFlow, and channels for Android and Ktor backends.

Kotlin Coroutines and Flow

Kotlin Coroutines provide structured concurrency for Android and backend development. Combined with Flow for reactive streams, they offer a powerful async programming model.

Suspend Functions

// Suspend functions can pause without blocking the thread
suspend fun fetchUser(id: String): User {
    return withContext(Dispatchers.IO) {
        api.getUser(id)  // Network call
    }
}

// Call from coroutine
viewModelScope.launch {
    val user = fetchUser("123") // Non-blocking suspend
    updateUI(user)
}

Coroutine Scopes and Builders

// launch: fire-and-forget
val job = scope.launch {
    delay(1000)
    println("Done after 1 second")
}

// async: returns a Deferred (like Future/Promise)
val deferredUser = scope.async {
    fetchUser("123")
}
val user = deferredUser.await()

// Concurrent execution with async
suspend fun loadDashboard(): Dashboard {
    coroutineScope {
        val user = async { fetchUser(userId) }
        val orders = async { fetchOrders(userId) }
        val recommendations = async { fetchRecommendations(userId) }
        
        // Await all concurrently
        Dashboard(
            user = user.await(),
            orders = orders.await(),
            recommendations = recommendations.await()
        )
    }
}

Dispatchers

// IO dispatcher for network/disk operations
withContext(Dispatchers.IO) {
    database.query("SELECT * FROM users")
}

// Default dispatcher for CPU-intensive work
withContext(Dispatchers.Default) {
    processLargeDataset(data)
}

// Main dispatcher for UI operations (Android)
withContext(Dispatchers.Main) {
    binding.textView.text = result
}

// Custom dispatcher with thread pool
val customDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()

Structured Concurrency

// Parent scope cancels all children
class UserRepository(private val scope: CoroutineScope) {
    
    fun loadUserData(userId: String) = scope.launch {
        try {
            val user = fetchUser(userId)
            val orders = fetchOrders(userId)
            processUserData(user, orders)
        } catch (e: CancellationException) {
            // Clean up resources
            throw e // Always rethrow CancellationException
        } catch (e: Exception) {
            handleError(e)
        }
    }
}

// SupervisorScope: child failures don't cancel siblings
supervisorScope {
    val result1 = async { riskyOperation1() }
    val result2 = async { riskyOperation2() }
    
    // Even if result1 fails, result2 still runs
    val r1 = try { result1.await() } catch (e: Exception) { null }
    val r2 = try { result2.await() } catch (e: Exception) { null }
}

Flow for Reactive Streams

// Cold flow: executes on collection
fun getNumbers(): Flow<Int> = flow {
    for (i in 1..10) {
        delay(100)
        emit(i)
    }
}

// Collect the flow
viewModelScope.launch {
    getNumbers()
        .filter { it % 2 == 0 }
        .map { it * it }
        .collect { value ->
            println("Received: $value")
        }
}

// Flow from database (Room)
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :id")
    fun observeUser(id: String): Flow<User>  // Emits on every DB change
}

// Collect in ViewModel
viewModelScope.launch {
    userDao.observeUser(userId)
        .catch { e -> emit(User.empty()) } // Handle errors
        .collect { user ->
            _uiState.value = UiState.Success(user)
        }
}

StateFlow and SharedFlow

// StateFlow: holds current state, replays to new collectors
class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    init {
        viewModelScope.launch {
            try {
                val user = fetchUser(userId)
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message)
            }
        }
    }
}

// Collect in Activity
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { state ->
            when (state) {
                is UiState.Loading -> showLoading()
                is UiState.Success -> showUser(state.user)
                is UiState.Error -> showError(state.message)
            }
        }
    }
}

// SharedFlow: for events (no replay by default)
class EventBus {
    private val _events = MutableSharedFlow<AppEvent>()
    val events = _events.asSharedFlow()
    
    suspend fun publish(event: AppEvent) {
        _events.emit(event)
    }
}

Channels

// Channel: for communication between coroutines
val channel = Channel<Int>(capacity = Channel.BUFFERED)

// Producer
launch {
    for (i in 1..5) {
        channel.send(i)
    }
    channel.close()
}

// Consumer
launch {
    for (value in channel) {
        println("Received: $value")
    }
}

// Producer coroutine builder
fun produceNumbers() = produce<Int> {
    for (i in 1..5) {
        send(i)
    }
}

Error Handling

// CoroutineExceptionHandler for uncaught exceptions
val handler = CoroutineExceptionHandler { _, exception ->
    println("Caught exception: ${exception.message}")
    // Log to crash reporting
}

val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main + handler)

scope.launch {
    throw RuntimeException("Something went wrong")
    // Caught by handler, scope continues
}

// Flow error handling
userFlow
    .retry(3) { cause -> cause is NetworkException }
    .catch { e -> emit(defaultValue) }
    .onEach { value -> processValue(value) }
    .launchIn(viewModelScope)

Ktor: Coroutines in Backend

// Ktor server with coroutines
fun Application.configureRouting() {
    routing {
        get("/users/{id}") {
            val id = call.parameters["id"]!!
            
            // Suspend function in route handler
            val user = userRepository.findById(id)
                ?: return@get call.respond(HttpStatusCode.NotFound)
            
            call.respond(user)
        }
        
        // WebSocket with Flow
        webSocket("/stream") {
            userRepository.observeAll()
                .collect { user ->
                    send(Json.encodeToString(user))
                }
        }
    }
}

// Parallel database queries
suspend fun getUserDashboard(userId: String): Dashboard = coroutineScope {
    val user = async { userRepository.findById(userId) }
    val orders = async { orderRepository.findByUserId(userId) }
    val stats = async { analyticsRepository.getUserStats(userId) }
    
    Dashboard(user.await()!!, orders.await(), stats.await())
}

Testing Coroutines

@Test
fun testFetchUser() = runTest {
    // runTest replaces runBlocking for tests
    val mockApi = mockk<UserApi>()
    coEvery { mockApi.getUser("123") } returns User(id = "123", name = "Alice")
    
    val repository = UserRepository(mockApi)
    val user = repository.getUser("123")
    
    assertEquals("Alice", user.name)
}

@Test
fun testFlow() = runTest {
    val flow = flowOf(1, 2, 3)
    val results = flow.toList()
    assertEquals(listOf(1, 2, 3), results)
}

Summary

Kotlin Coroutines and Flow provide:

  • Suspend functions: Non-blocking async code that reads like synchronous
  • Structured concurrency: Parent cancels children, preventing leaks
  • Dispatchers: Route work to appropriate thread pools
  • Flow: Reactive streams with rich operators
  • StateFlow/SharedFlow: Hot flows for UI state and events
  • Channels: Safe communication between coroutines

The combination makes async Android and backend code maintainable and testable.