这是导致无限重组问题的代码
主要活动
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配置
FirstScreen
和SecondScreen
(初始NavHost
composition
( FirstScreen
观测到值为0
的整数state
- 点击按钮后
state
变为1
FirstScreen
re-composes
,满足条件(state==1
(,执行1st
时间的导航- NavHost
re-composes
FirstScreen's
state
保持1
,仍然满足条件(state==1
(,在2nd
时间内再次执行导航- NavHost
re-composes
FirstScreen's
state
保持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()
}
}
}
}
}
请在这里查看我的答案