LocalLifecycleOwner 配方
此配方演示了如何在 Navigation 3 条目中使用 LifecycleResumeEffect,以根据条目的生命周期状态暂停和恢复工作。
运作方式
在 Navigation 3 中,默认情况下,每个 NavEntry 都会通过 LocalLifecycleOwner.current 获得自己的 LifecycleOwner。这意味着,条目内的任何生命周期感知型组件都会自动限定为 NavEntry 的范围。
使用对话框场景的 LifecycleResumeEffect
-
RouteA(屏幕):
- 使用限定为
NavEntry的LocalLifecycleOwner.current的LifecycleResumeEffect(Unit),以便在RESUMED状态下推进LinearProgressIndicator。 - 每当
progressValue达到1f时,都会自动将其重置为0f。
- 使用限定为
-
RouteB(对话框):
- 使用
DialogSceneStrategy.dialog()配置为对话框。 - 当用户打开 RouteB 对话框时,RouteA 会在
STARTED状态下(离开RESUMED)在对话框后面保持可见。 LifecycleResumeEffect会调用onPauseOrDispose,暂停进度指示器。- 当对话框被关闭时,RouteA 会返回到
RESUMED,并且LifecycleResumeEffect会自动恢复进度指示器。
- 使用
/* * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.nav3recipes.lifecycleowner import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.snap import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.scene.DialogSceneStrategy import androidx.navigation3.ui.NavDisplay import com.example.nav3recipes.content.ContentGreen import com.example.nav3recipes.ui.setEdgeToEdgeConfig import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import kotlin.time.Duration.Companion.milliseconds @Serializable private data object RouteA : NavKey @Serializable private data object RouteB : NavKey class LifecycleOwnerActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { setEdgeToEdgeConfig() super.onCreate(savedInstanceState) setContent { val backStack = rememberNavBackStack(RouteA) val dialogStrategy = remember { DialogSceneStrategy<NavKey>() } NavDisplay( backStack = backStack, onBack = backStack::removeLastOrNull, sceneStrategies = listOf(dialogStrategy), entryProvider = entryProvider { entry<RouteA> { LogLifecycleEffect("ScreenA") ContentGreen("Screen A") { var progressValue by remember { mutableFloatStateOf(0f) } val coroutineScope = rememberCoroutineScope() val animatedProgress by animateFloatAsState( targetValue = progressValue, animationSpec = if (progressValue == 0f) snap() else tween(durationMillis = 100, easing = LinearEasing), label = "ProgressAnimation" ) // LifecycleResumeEffect runs only while ScreenA is in the RESUMED state. // When the dialog opens, ScreenA transitions to PAUSED, which calls onPauseOrDispose. LifecycleResumeEffect(Unit) { val job = coroutineScope.launch { while (true) { delay(100.milliseconds) progressValue += 0.01f if (progressValue >= 1f) { progressValue = 0f } } } onPauseOrDispose { job.cancel() } } Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) ) { LinearProgressIndicator(progress = { animatedProgress }) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = dropUnlessResumed { backStack.add(RouteB) }) { Text("Open Dialog") } } } } entry<RouteB>(metadata = DialogSceneStrategy.dialog()) { LogLifecycleEffect("Dialog") AlertDialog( onDismissRequest = backStack::removeLastOrNull, title = { Text("Dialog") }, text = { Text("Screen A is no longer in resumed state, pausing the progress indicator.") }, confirmButton = { Button(onClick = dropUnlessResumed { backStack.removeLastOrNull() }) { Text("Dismiss Dialog") } } ) } } ) } } } @Composable private fun LogLifecycleEffect(screenName: String) { LifecycleEventEffect(Lifecycle.Event.ON_CREATE) { Log.d("LifecycleOwner", "$screenName: ON_CREATE") } LifecycleEventEffect(Lifecycle.Event.ON_START) { Log.d("LifecycleOwner", "$screenName: ON_START") } LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { Log.d("LifecycleOwner", "$screenName: ON_RESUME") } LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) { Log.d("LifecycleOwner", "$screenName: ON_PAUSE") } LifecycleEventEffect(Lifecycle.Event.ON_STOP) { Log.d("LifecycleOwner", "$screenName: ON_STOP") } // Note that ON_DESTROY event is not observable from composables. }