Używaj współprogramów Kotlin z komponentami dopasowanymi do cyklu życia

Współprogramy Kotlin udostępniają interfejs API, który umożliwia pisanie kodu asynchronicznego. Dzięki współprogramom Kotlin możesz zdefiniować CoroutineScope, który pomaga zarządzać czasem działania współprogramów. Każda operacja asynchroniczna jest wykonywana w określonym zakresie.

Komponenty uwzględniające cykl życia zapewniają najwyższej jakości obsługę współprogramów w zakresach logicznych w aplikacji. Z tego dokumentu dowiesz się, jak efektywnie korzystać ze współprogramów z komponentami uwzględniającymi cykl życia.

Dodawanie zależności

Wbudowane zakresy współprogramów opisane w tym artykule znajdują się w interfejsie Lifecycle API. Pamiętaj, aby podczas korzystania z tych zakresów dodać odpowiednie zależności.

  • W przypadku narzędzi ViewModel w Compose użyj implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version").
  • W przypadku narzędzi Lifecycle w Compose użyj implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version").

Zakresy współprogramów uwzględniające cykl życia

Biblioteki Compose i Lifecycle udostępniają te wbudowane zakresy, których możesz używać w swojej aplikacji.

ViewModelScope

Dla każdego ViewModel w aplikacji jest zdefiniowany ViewModelScope. Każdy współprogram uruchomiony w tym zakresie jest automatycznie anulowany, jeśli ViewModel zostanie wyczyszczony. Współprogramy są przydatne, gdy masz do wykonania zadanie, które musi zostać wykonane tylko wtedy, gdy ViewModel jest aktywny. Jeśli na przykład obliczasz dane dla układu, powinieneś ograniczyć zakres pracy do ViewModel, aby w przypadku wyczyszczenia ViewModel praca została automatycznie anulowana, co pozwoli uniknąć zużywania zasobów.

Dostęp do CoroutineScope elementu ViewModel możesz uzyskać za pomocą właściwości viewModelScope elementu ViewModel, jak pokazano w tym przykładzie:

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

W bardziej zaawansowanych przypadkach użycia możesz przekazać niestandardowy CoroutineScope bezpośrednio do konstruktora ViewModel, aby zastąpić domyślny viewModelScope. To podejście zapewnia większą kontrolę i elastyczność, szczególnie w przypadku:

  • Testowania: umożliwia wstrzyknięcie TestScope, co ułatwia kontrolowanie czasu i weryfikowanie zachowania współprogramu w testach jednostkowych.

  • Konfiguracji niestandardowej: możesz skonfigurować zakres za pomocą konkretnego CoroutineDispatcher (np. Dispatchers.Default w przypadku złożonych obliczeń) lub niestandardowego CoroutineExceptionHandler, zanim ViewModel rozpocznie pracę.

Zakresy powiązane z kompozycją

Efekty uboczne, takie jak animacje, wywołania sieciowe czy timery, muszą być ograniczone do cyklu życia funkcji kompozycyjnej. Dzięki temu, gdy funkcja kompozycyjna opuści ekran (zakończy kompozycję), wszystkie uruchomione współprogramy zostaną automatycznie anulowane, aby zapobiec wyciekom pamięci.

Compose udostępnia interfejs API LaunchedEffect, który umożliwia deklaratywne obsługiwanie zakresu kompozycji.

LaunchedEffect tworzy CoroutineScope, który umożliwia uruchamianie funkcji zawieszających. Zakres jest powiązany z cyklem życia kompozycji funkcji kompozycyjnej, a nie z cyklem życia aktywności hosta.

  • Wejście: współprogram rozpoczyna się, gdy funkcja kompozycyjna wchodzi w skład kompozycji.
  • Wyjście: współprogram jest anulowany, gdy funkcja kompozycyjna opuszcza kompozycję.
  • Ponowne uruchomienie: jeśli zmieni się dowolny klucz przekazany do LaunchedEffect, dotychczasowy współprogram zostanie anulowany i uruchomiony zostanie nowy.

Ten przykład pokazuje, jak używać LaunchedEffect do tworzenia animacji pulsowania. Współprogram jest powiązany z obecnością funkcji kompozycyjnej w kompozycji i reaguje na zmiany konfiguracji:

// 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)
    }
}

Więcej informacji o LaunchedEffect znajdziesz w artykule Efekty uboczne w Compose.

Kolekcja przepływów uwzględniająca cykl życia

Aby bezpiecznie zbierać przepływy w Jetpack Compose, użyj interfejsu API collectAsStateWithLifecycle. Ta pojedyncza funkcja przekształca Flow w obiekt State Compose i automatycznie zarządza subskrypcją cyklu życia. Domyślnie zbieranie rozpoczyna się, gdy cykl życia jest STARTED, a kończy się, gdy cykl życia jest STOPPED. Aby zastąpić to domyślne zachowanie, przekaż parametr minActiveState z wybraną metodą cyklu życia, np. Lifecycle.State.RESUMED.

Ten przykład pokazuje, jak zbierać StateFlow ViewModel w funkcji kompozycyjnej:

@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)
    /* ... */
}

Równoległe zbieranie wielu przepływów

W Compose możesz zbierać wiele przepływów równolegle, deklarując wiele zmiennych stanu. Ponieważ collectAsStateWithLifecycle zarządza własnym zakresem bazowym, równoległe zbieranie jest obsługiwane automatycznie:

@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()

    // ...
}

Asynchroniczne obliczanie wartości za pomocą przepływów

Gdy musisz obliczyć wartości asynchronicznie, użyj StateFlow z operatorem stateIn.

Ten fragment kodu używa standardowego Flow przekształconego w StateFlow. Parametr WhileSubscribed(5000) utrzymuje subskrypcję aktywną przez pięć sekund po zniknięciu interfejsu, aby obsługiwać zmiany konfiguracji.

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

Użyj collectAsStateWithLifecycle, aby przekształcić zebrane wartości w Compose State, dzięki czemu interfejs będzie mógł reagować na zmiany danych.

Więcej informacji o stanie znajdziesz w artykule Stan i Jetpack Compose.

Dodatkowe materiały

Wyświetlanie treści

Przykłady