Jetpack Compose,在使用NavHost导航时,可变状态会导致无限重组



这是导致无限重组问题的代码

主要活动

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
val navController = rememberNavController()
val viewModel : MainViewModel by viewModel()
val state by viewModel.state.observeAsState()
NavHost(navController = navController, startDestination = "firstScreen") {
composable("firstScreen") { FirstScreen(
navigate = {
navController.navigate("secondScreen")
}, updateState = {
viewModel.getState()
},
state
)}
composable("secondScreen") { SecondScreen() }
}
}
}
}

ViewModel

class MainViewModel : ViewModel() {
//var state = MutableStateFlow(0)
private val _state = MutableLiveData(0)
val state: LiveData<Int> = _state
fun getState()  {
_state.value = 1
}
}

第一屏

@Composable
fun FirstScreen(
navigate: () -> Unit,
updateState: () -> Unit,
state: Int?
) {
Log.e("state",state.toString())
Button(onClick = {
updateState()
}) {
Text(text = "aaaaaaaa")
}
if(state == 1) {
Log.e("navigate",state.toString())
navigate()
}
}

第二屏幕

@Composable
fun SecondScreen() {...}

按下按钮可更改视图模型中的状态,作为反应,如果它变为1,则会触发导航到第二个屏幕,但第一个屏幕会无限地重新组合并阻止整个过程

编辑

@Composable
fun FirstScreen(
navigate: () -> Unit,
updateState: () -> Unit,
state: Int?
) {

Log.e("state",state.toString())
Button(onClick = {
updateState()
}) {
Text(text = "aaaaaaaa")
}
LaunchedEffect(state) {
if (state == 1) {
Log.e("navigate", state.toString())
navigate()
}
}
}

这解决了的问题

这是因为您基于一个条件属性进行导航,该属性是FirstScreen可组合的一部分,并且对该属性的更改不在FirstScreen's范围内,如果该条件属性的值没有更改,则每次NavHost更新时,它都会评估其块,在您的情况下,state仍然是1,并始终执行其块。

if(state == 1) {
...
navigate() // navigation
}

你的经历可以通过以下事件总结:

  • Navhost配置FirstScreenSecondScreen(初始NavHostcomposition(
  • FirstScreen观测到值为0的整数state
  • 点击按钮后state变为1
  • FirstScreenre-composes,满足条件(state==1(,执行1st时间的导航
  • NavHostre-composes
  • FirstScreen'sstate保持1,仍然满足条件(state==1(,在2nd时间内再次执行导航
  • NavHostre-composes
  • FirstScreen'sstate保持1,满足条件(state==1(,在3rd时间内再次执行导航
  • 循环永远不会结束

基于官方文件,

您应该只将navigate((作为回调的一部分调用,而不是作为部分调用您的可组合文件本身,以避免在每个重组。

我建议将navigation视为一次性事件,在LaunchedEffect内进行,并从SharedFlow发射中观察到。以下是解决您问题的简短方法。

有一个密封类UiEvent

sealed class UiEvent {
data class Navigate(val params: Any?): UiEvent()
}

像这个一样修改你的ViewModel

class MainViewModel : ViewModel() {
...
private val _oneTimeEvent = MutableSharedFlow<UiEvent>()
val oneTimeEvent = _oneTimeEvent.asSharedFlow()
...
fun navigate()  {
if (_state.value == 1) {
viewModelScope.launch {
_oneTimeEvent.emit(UiEvent.Navigate(1))
}
}
}
}

,然后通过FirstScreen中的LaunchedEffect进行观察

@Composable
fun FirstScreen(
navigate: () -> Unit,
..
) {
...
...

LaunchedEffect(Unit) {
mainViewModel.oneTimeEvent.collectLatest { uiEvent ->
when (uiEvent) {
is UiEvent.Navigate -> {
navigate()
}
}
}
}
}

请在这里查看我的答案

最新更新