【问题标题】:Compose StateFlow being kept even after ViewModel that creates it is recreated即使在重新创建创建它的 ViewModel 之后,仍会保留 Compose StateFlow
【发布时间】:2022-06-15 04:35:35
【问题描述】:

我在这里遇到了一个非常奇怪的问题。我有一个ViewModel 有一个StateFlowViewModel 在特定情况下重新创建,并将其 StateFlow 值设置为 0。

我还有一个 Compose 视图,它读取这个 StateFlow 的值并根据它显示文本。 例如,然后我将该状态更改为 2。然后重新创建整个 Compose 视图和ViewModel

但是,当我重新创建整个视图时,ViewModel 在短时间内,StateFlow 保持它的旧状态(即使 ViewModel 与视图一起重新创建并且状态设置为 0),并且然后切换到新的零(这仅在您进行下面提到的更改时才有效)。

如果我们的列表具有不同数量的项目,这可能会导致崩溃,并且我们在重新创建视图时传递它们,因为这样我们将读取不存在的索引值并且我们的应用程序将崩溃。

将列表ViewModelTwo(mutableListOf("text4")) 更改为ViewModelTwo(mutableListOf("text4", "text5", "text6")) 将停止崩溃。但是看看日志,你就会知道发生了什么。首先它变为 2,然后变为 0,这是默认值。

我为 Compose-Jb 设置了 github 存储库。可以在 Android Studio 中打开:https://github.com/bnovakovic/composableIssue

很抱歉使用了 android compose 标签,但我找不到 Compose-JB 标签。 为方便起见,这里是代码 sn-ps。

感谢任何帮助

Main.kt

@Composable
@Preview
fun App(viewModelOne: ViewModelOne) {
    val showComposable by viewModelOne.stateOne.collectAsState()

    MaterialTheme {
        // Depending on the state we decide to create different ViewModel
        val viewModelTwo: ViewModelTwo = when (showComposable) {
            0 -> ViewModelTwo(mutableListOf("text1", "text2", "text3"))
            1 -> ViewModelTwo(mutableListOf("text4"))
            else -> ViewModelTwo(mutableListOf("blah1", "blah2", "blah3"))
        }

        // New composable is always created with new ViewModelTwo that has default index of 0, yet the app still crashes
        TestComposableTwo(viewModelTwo)
        Row {
            Button(onClick = {
                viewModelOne.changeState()
            }) {
                Text("Click button below, than me")
            }
        }
    }
}

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        val viewModelOne = ViewModelOne();
        App(viewModelOne)
    }
}

TestComposableView

@Composable
fun TestComposableTwo(viewModelTwo: ViewModelTwo) {

    val currentIndex by viewModelTwo.currentListItem.collectAsState()
    println("Index is: $currentIndex")
    Column(
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight()

    ) {
        // At the point where we recreate this composable view, currentIndex keeps it's old value, and then changes it
        // to the new one causing the app to crash since new list does not have index of 1
        Text(text = viewModelTwo.stringList[currentIndex])
        Button(onClick = {
            viewModelTwo.changeIndex()
        }) {
            Text("Click me before clicking button above")
        }
    }

}

ViewModel1

class ViewModelOne {
    private val viewModelScope = CoroutineScope(Dispatchers.IO)
    private val _stateOne = MutableStateFlow(0)
    val stateOne = _stateOne.asStateFlow()

    fun changeState() {
        viewModelScope.launch {
            val currentValue = stateOne.value + 1
            _stateOne.emit(currentValue)
        }
    }
}

ViewModel2

class ViewModelTwo(val stringList: List<String>) {
    private val viewModelScope = CoroutineScope(Dispatchers.IO)
    private val _currentListItem = MutableStateFlow(0)
    val currentListItem = _currentListItem.asStateFlow()

    fun changeIndex() {
        viewModelScope.launch {
            _currentListItem.emit(2)
        }
    }
}

【问题讨论】:

    标签: android-jetpack-compose android-jetpack


    【解决方案1】:

    根据第一个 ViewModel 中的值在 Composable 中创建新的 ViewModelTwo 似乎过于复杂。尝试让您的 ViewModel 处理逻辑,而您的 Composable 只显示数据。

    我建议使用带有单个 MutableStateFlow 的单个 ViewModel。 在这种情况下,您不需要发出或 viewModelScope。 像这样的东西应该可以工作:

    class MainViewModel {
        private val _state = MutableStateFlow(State(listOf("text1", "text2", "text3"), 0))
        val state: StateFlow<State> = _state
    
        fun onChangeStateClick() {
            _state.value = State(listOf("text4"), 0) // Or other logic here if needed
        }
    
        fun onChangeIndexClick() {
            _state.update {
                if (it.currentIndex < it.stringList.count() - 1) {
                    it.copy(currentIndex = it.currentIndex + 1)
                } else it.copy(currentIndex = 0)
            }
        }
    
        data class State(
            val stringList: List<String>,
            val currentIndex: Int
        )
    }
    
    @Composable
    @Preview
    fun App(viewModel: MainViewModel) {
        val state by viewModel.state.collectAsState()
    
        MaterialTheme {
            TestComposableTwo(
                state.stringList[state.currentIndex],
                onButtonClick = { viewModel.onChangeIndexClick() }
            )
            Row {
                Button(onClick = {
                    viewModel.onChangeStateClick()
                }) {
                    Text("Click button below, than me")
                }
            }
        }
    }
    
    @Composable
    fun TestComposableTwo(text: String, onButtonClick: () -> Unit) {
    
        Column(
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
    
        ) {
            Text(text = text)
            Button(onClick = onButtonClick) {
                Text("Click me before clicking button above")
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-10
      • 1970-01-01
      • 2019-03-16
      • 1970-01-01
      • 2019-07-26
      • 1970-01-01
      • 2020-08-28
      • 2011-06-12
      相关资源
      最近更新 更多