kotlin Jetpack合成,使用NavHost导航时可变状态导致无限重组

dzjeubhm  于 2022-11-16  发布在  Kotlin
关注(0)|答案(1)|浏览(189)

下面是导致无限重组问题的代码
主要活动

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

检视模型

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() {...}

按下该按钮可更改视图模型中的状态,并且作为React,如果该按钮更改为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()
        }
    }

}

这就解决了问题

kr98yfug

kr98yfug1#

这是因为你是基于一个条件属性来导航的,这个属性是FirstScreen可组合函数的一部分,对这个属性的更改超出了FirstScreen's的作用域,如果这个条件属性的值没有改变,那么每次NavHost更新时,它总是会计算它的块,在你的例子中,state仍然是1,并且总是会执行它的块。

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

您所经历的事件可总结如下:

  • Navhost配置FirstScreenSecondScreen(初始值为NavHostcomposition
  • FirstScreen观察值为0的整数state
  • 单击按钮后,state变为1
  • FirstScreenre-composes,满足条件(state==1),执行**1st**时间的导航
  • 导航主机re-composes
  • FirstScreen'sstate仍为1,仍满足条件(state==1),再次执行导航***********次
  • 导航主机re-composes
  • FirstScreen'sstate保持1,满足条件(state==1),再次执行导航**3rd**次
  • 循环往复。

基于官方的Docs
您应该只将navigate()作为回调的一部分调用,而不是作为可组合对象本身的一部分调用,以避免在每次重新组合时调用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()
                }
            }
        }
    }
}

请参见my answer here

相关问题