Dùng coroutine của Kotlin với các thành phần nhận biết vòng đời

Coroutine của Kotlin cung cấp một API cho phép bạn viết mã không đồng bộ. Với coroutine của Kotlin, bạn có thể xác định một CoroutineScope để giúp bạn quản lý thời điểm coroutine sẽ chạy. Mỗi thao tác không đồng bộ chạy trong một phạm vi cụ thể.

Các thành phần nhận biết vòng đời cung cấp sự hỗ trợ tốt nhất cho coroutine trong phạm vi logic của ứng dụng. Tài liệu này giải thích cách sử dụng coroutine một cách hiệu quả thông qua các thành phần nhận biết vòng đời.

Thêm phần phụ thuộc

Các phạm vi coroutine tích hợp sẵn được mô tả trong chủ đề này nằm trong Lifecycle API. Hãy nhớ thêm các phần phụ thuộc thích hợp khi sử dụng các phạm vi này.

  • Đối với các tiện ích ViewModel trong Compose, hãy sử dụng implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version").
  • Đối với các tiện ích Vòng đời trong Compose, hãy sử dụng implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version").

Phạm vi coroutine nhận biết vòng đời

Compose và các thư viện Lifecycle cung cấp các phạm vi tích hợp sau đây mà bạn có thể dùng trong ứng dụng của mình.

ViewModelScope

Một ViewModelScope được khai báo cho từng ViewModel trong ứng dụng của bạn. Hệ thống sẽ tự động huỷ mọi coroutine chạy trong phạm vi này nếu ViewModel bị xoá. Coroutine rất hữu ích trong trường hợp này khi bạn chỉ cần hoàn thành công việc nếu ViewModel đang hoạt động. Ví dụ: nếu đang tính toán một số dữ liệu cho một bố cục, bạn nên đặt phạm vi của công việc thành ViewModel để nếu ViewModel bị xoá, công việc này sẽ tự động bị huỷ để tránh tiêu tốn tài nguyên.

Bạn có thể truy cập vào CoroutineScope của ViewModel thông qua thuộc tính viewModelScope của ViewModel, như trong ví dụ sau:

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

Đối với các trường hợp sử dụng nâng cao hơn, bạn có thể truyền trực tiếp một CoroutineScope tuỳ chỉnh vào hàm khởi tạo của ViewModel để thay thế viewModelScope mặc định. Phương pháp này mang lại nhiều quyền kiểm soát và tính linh hoạt hơn, đặc biệt là đối với:

  • Kiểm thử: Phương pháp này cho phép bạn chèn một TestScope, giúp bạn dễ dàng kiểm soát thời gian và xác minh hành vi của coroutine trong các kiểm thử đơn vị.

  • Cấu hình tuỳ chỉnh: Bạn có thể định cấu hình phạm vi bằng một CoroutineDispatcher cụ thể (chẳng hạn như Dispatchers.Default cho hoạt động tính toán phức tạp) hoặc một CoroutineExceptionHandler tuỳ chỉnh trước khi ViewModel bắt đầu hoạt động.

Các phạm vi liên kết với thành phần

Các tác dụng phụ như ảnh động, lệnh gọi mạng hoặc bộ hẹn giờ phải được giới hạn trong vòng đời của thành phần kết hợp. Bằng cách này, khi một thành phần kết hợp rời khỏi màn hình (thoát khỏi thành phần), mọi coroutine đang chạy sẽ tự động bị huỷ để ngăn chặn tình trạng rò rỉ bộ nhớ.

Compose cung cấp API LaunchedEffect để xử lý phạm vi Thành phần theo cách khai báo.

LaunchedEffect tạo một CoroutineScope cho phép bạn chạy các hàm tạm ngưng. Phạm vi này gắn liền với vòng đời Composition của thành phần kết hợp, chứ không phải Lifecycle của Hoạt động lưu trữ.

  • Nhập: Coroutine bắt đầu khi thành phần kết hợp tham gia thành phần.
  • Thoát: Coroutine sẽ bị huỷ khi thành phần kết hợp rời khỏi thành phần.
  • Khởi chạy lại: Nếu có khoá nào được truyền đến LaunchedEffect thay đổi, coroutine hiện có sẽ bị huỷ và một coroutine mới sẽ được khởi chạy.

Ví dụ sau đây minh hoạ cách sử dụng LaunchedEffect để tạo một ảnh động nhấp nháy. Coroutine được liên kết với sự hiện diện của thành phần kết hợp trong thành phần và phản ứng với các thay đổi về cấu hình:

// Allow the pulse rate to be configured, so it can be sped up if the user is running
// out of time
var pulseRateMs by remember { mutableLongStateOf(3000L) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes
    while (isActive) {
        delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user
        alpha.animateTo(0f)
        alpha.animateTo(1f)
    }
}

Để biết thêm thông tin về LaunchedEffect, hãy xem bài viết Hiệu ứng phụ trong Compose.

Thu thập quy trình nhận biết vòng đời

Để thu thập các luồng dữ liệu một cách an toàn trong Jetpack Compose, hãy dùng API collectAsStateWithLifecycle. Hàm duy nhất này chuyển đổi Flow thành một đối tượng State Compose và tự động quản lý việc đăng ký vòng đời cho bạn. Theo mặc định, quá trình thu thập bắt đầu khi vòng đời là STARTED và dừng khi vòng đời là STOPPED. Để ghi đè hành vi mặc định này, hãy truyền tham số minActiveState bằng phương thức vòng đời mà bạn muốn, chẳng hạn như Lifecycle.State.RESUMED.

Ví dụ sau đây minh hoạ cách thu thập StateFlow của ViewModel trong một thành phần kết hợp:

@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {

    val messages by conversationViewModel.messages.collectAsStateWithLifecycle()

    ConversationScreen(
        messages = messages,
        onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) }
    )
}

@Composable
private fun ConversationScreen(
    messages: List<Message>,
    onSendMessage: (Message) -> Unit
) {

    MessagesList(messages, onSendMessage)
    /* ... */
}

Thu thập song song nhiều luồng

Trong Compose, bạn có thể thu thập song song nhiều luồng bằng cách khai báo nhiều biến trạng thái. Vì collectAsStateWithLifecycle quản lý phạm vi cơ bản của riêng nó, nên quá trình thu thập song song sẽ được xử lý tự động:

@Composable
fun DashboardScreen(viewModel: DashboardViewModel = viewModel()) {
    // Both flows are collected safely in parallel and will emit updates when either changes, the composables will recompose
    val userData by viewModel.userFlow.collectAsStateWithLifecycle()
    val feedData by viewModel.feedFlow.collectAsStateWithLifecycle()

    // ...
}

Tính toán các giá trị một cách không đồng bộ bằng cách sử dụng các luồng

Khi bạn cần tính toán các giá trị một cách không đồng bộ, hãy sử dụng StateFlow với toán tử stateIn.

Đoạn mã sau đây sử dụng một Flow tiêu chuẩn được chuyển đổi thành StateFlow. Tham số WhileSubscribed(5000) duy trì trạng thái hoạt động của gói thuê bao trong 5 giây sau khi giao diện người dùng biến mất để xử lý các thay đổi về cấu hình.

val uiState: StateFlow<Result> = flow {
    emit(repository.fetchData())
}
.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = Result.Loading
)

Dùng collectAsStateWithLifecycle để chuyển đổi các giá trị đã thu thập thành State của Compose, nhờ đó giao diện người dùng có thể cập nhật một cách phản ứng bất cứ khi nào dữ liệu thay đổi.

Để biết thêm thông tin về trạng thái, hãy xem bài viết Trạng thái và Jetpack Compose.

Tài nguyên khác

Xem nội dung

Mẫu