gpt4 book ai didi

android - Jetpack Compose Navigation 无限加载屏幕

转载 作者:行者123 更新时间:2023-12-05 01:49:22 27 4
gpt4 key购买 nike

我正在尝试使用单个 Activity 实现 Navigation 并且多个 Composable 屏幕。

这是我的 NavHost:

@Composable
@ExperimentalFoundationApi
fun MyNavHost(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
startDestination: String = HOME.route,
viewModelProvider: ViewModelProvider,
speech: SpeechHelper
) = NavHost(
modifier = modifier,
navController = navController,
startDestination = startDestination
) {
composable(route = HOME.route) {
with(viewModelProvider[HomeViewModel::class.java]) {
HomeScreen(
speech = speech,
viewModel = this,
modifier = Modifier.onKeyEvent { handleKeyEvent(it, this) }
) {
navController.navigateTo(it)
}
}
}

composable(route = Destination.VOLUME_SETTINGS.route) {
VolumeSettingsScreen(
viewModelProvider[VolumeSettingsViewModel::class.java]
) { navController.navigateUp() }
}
}

fun NavHostController.navigateTo(
navigateRoute: String,
willGoBackTo: String = HOME.route
): Unit = navigate(navigateRoute) {
popUpTo(willGoBackTo) { inclusive = true }
}

我的屏幕是这样的:

@Composable
fun HomeScreen(
speech: SpeechHelper,
viewModel: HomeViewModel,
modifier: Modifier,
onNavigationRequested: (String) -> Unit
) {

MyBlindAssistantTheme {
val requester = remember { FocusRequester() }
val uiState by viewModel.uiState.collectAsStateWithLifecycle(
initialValue = UiState.Speak(
R.string.welcome_
.withStrResPlaceholder(R.string.text_home_screen)
.toSpeechUiModel()
)
)

uiState?.let {
when (it) {
is UiState.Speak -> speech.speak(it.speechUiModel)
is UiState.SpeakRes -> speech.speak(it.speechResUiModel.speechUiModel())
is UiState.Navigate -> onNavigationRequested(it.route)
}
}

Column(
modifier
.focusRequester(requester)
.focusable(true)
.fillMaxSize()
) {
val rowModifier = Modifier.weight(1f)

Row(rowModifier) {...}

}

LaunchedEffect(Unit) {
requester.requestFocus()
}
}
}

这是 View 模型:

class HomeViewModel : ViewModel() {
private val mutableUiState: MutableStateFlow<UiState?> = MutableStateFlow(null)
val uiState = mutableUiState.asStateFlow()


fun onNavigateButtonClicked(){
mutableUiState.tryEmit(Destination.VOLUME_SETTINGS.route.toNavigationState())
}
}

单击按钮时,将调用 ViewModel 并发出 NavigateUiState...但它会在下一个屏幕加载后继续发出,这会导致无限屏幕重新加载。应该怎么做才能避免这种情况?

最佳答案

我用 2 个屏幕重新实现了您发布的代码,HomeScreenSettingScreen 并删除了 UiState 类的某些部分及其用法.

问题出在您的 HomeScreen 可组合项中,而不是出在 StateFlow 发射中。

你有这个mutableState

val uiState by viewModel.uiState.collectAsStateWithLifecycle(
initialValue = UiState.Speak
)

这是在执行 navigation 回调的 when block 中被观察到的

uiState?.let {
when (it) {
is UiState.Navigate -> {
onNavigationRequested(it.route)
}
UiState.Speak -> {
Log.d("UiState", "Speaking....")
}
}

当您的 ViewModel 函数被调用时

 fun onNavigateButtonClicked(){
mutableUiState.tryEmit(UiState.Navigate(Destination.SETTINGS_SCREEN.route))
}

它将更新 uiState,将其值设置为 Navigate,由 HomeScreen 观察,满足 when block 然后触发回调以导航到下一个屏幕。

现在基于官方Docs ,

You should only call navigate() as part of a callback and not as partof your composable itself, to avoid calling navigate() on everyrecomposition.

但在您的情况下,导航 是由观察到的 mutableState 触发的,而 mutableState 是您的 HomeScreen< 的一部分 可组合。

似乎当 navController 执行导航并且 NavHost 是一个 Composable

@Composable
public fun NavHost(
navController: NavHostController,
startDestination: String,
modifier: Modifier = Modifier,
route: String? = null,
builder: NavGraphBuilder.() -> Unit
) { ... }

它会执行一个re-composition,因为它,它会再次调用HomeScreen(HomeScreen is not re-composed,它的状态保持不变)并且因为 HomeScreen 的 UiState 值仍然设置为 Navigate,它满足 when block ,再次触发回调导航,NavHost 重新组合,然后创建一个无限循环

我所做的(而且非常丑陋)是我在 viewModel 中创建了一个 boolean 标志,用它有条件地包装回调,

uiState?.let {
when (it) {
is UiState.Navigate -> {
if (!viewModel.navigated) {
onNavigationRequested(it.route)
viewModel.navigated = true
} else {
// dirty empty else
}
}
UiState.Speak -> {
Log.d("UiState", "Speaking....")
}
}
}

然后将其设置为 true,防止循环。

我很难猜出你的 compose 实现结构,但我通常不会将我的一次性事件操作和 UiState 混合在一起,而是我有一个单独的 UiEvent 密封类,它将“一次性”分组诸如以下事件:

  • Snackbar
  • Toast
  • Navigation

并将它们作为 SharedFlow emission 发出,因为这些事件不需要任何初始状态或初始值。

继续,我创建了这个类

sealed class UiEvent {
data class Navigate(val route: String) : UiEvent()
}

ViewModel 中将其用作类型(在本例中为 Navigate),

 private val _event : MutableSharedFlow<UiEvent> = MutableSharedFlow()
val event = _event.asSharedFlow()

fun onNavigateButtonClicked(){
viewModelScope.launch {
_event.emit(UiEvent.Navigate(Destination.SETTINGS_SCREEN.route))
}
}

并通过 LaunchedEffect 以这种方式在 HomeScreen 中观察它,触发其中的导航,而无需将回调绑定(bind)到任何观察到的状态。

LaunchedEffect(Unit) {
viewModel.event.collectLatest {
when (it) {
is UiEvent.Navigate -> {
onNavigationRequested(it.route)
}
}
}
}

这种方法不会引入无限导航循环,也不再需要脏 bool 检查。

也看看这个S.O post, similar to your case

关于android - Jetpack Compose Navigation 无限加载屏幕,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74174614/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com