喷气背包组合导航旋转后数据丢失

P. *_*ohm 2 android-navigation android-jetpack-compose

当我在 jetpack compose 中旋转我的应用程序(带导航)时,所有数据都会重置

每次旋转后,它都会进入 Activity 的 onCreate 并再次创建 NavHost 和 ViewModel。我认为这会通过应用程序状态来处理

例子

起始活动:

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

        setContent {
            MyAppTheme() {
                MyAppApp()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

包含 NavHost 的应用程序:

@Composable
fun MyAppApp(
    appState:  MyAppAppState = rememberAppState()
) {
        NavHost(
            navController = appState.navController,
            startDestination = Screen.ProductAddPath.route
            )
        {
            composable(Screen.ProductAddPath.route) {
                ProductEditView(
                    vm = ProductEditViewModel (appState.context)
                )
            }
        }
}
Run Code Online (Sandbox Code Playgroud)

视图模型:

class ProductEditViewModel(context: Context) : ViewModel() {

    private val _productName = MutableStateFlow("qsd")
    val productName = _productName.asStateFlow()
    
    fun onProductNameChange(it: String) {
        _productName.value = it
    }


}
Run Code Online (Sandbox Code Playgroud)

我的看法 :

@Composable
fun ProductEditView(vm: ProductEditViewModel) {
    Surface( color = MaterialTheme.colorScheme.background) {
        val message by vm.productName.collectAsState()
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier.fillMaxWidth()
        ) {
            OutlinedTextField(
                value = message,
                onValueChange = { vm.onProductNameChange(it) },
            )
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我把一个工作项目放在github上

https://github.com/psohm/data-lost-rotation

Tha*_*oro 6

问题是ProductEditViewModel每次上下文发生MyAppAppState变化时都会重新创建,因此所有数据都会重置。init您可以通过使用向视图模型添加一个块来进行一个非常简单的测试println,您会注意到当您旋转屏幕,甚至更改设备的主题时,该init块的内容将再次被触发。通过从该参数中
删除此“问题”将不再发生。ContextProductEditViewModel

Context请注意,将 a 传递给 a是不好的做法ViewModel,当您采用这种方法时,您最终会陷入两种情况。
第一个是破坏 ViewModel 生命周期并使其相对于其声称的用途毫无用处(使数据独立于活动/片段/可组合项的生命周期)。
第二个是导致内存泄漏,很可能导致 ANR。

您可以查看此处此处,大致了解 ViewModel 的用途、其生命周期如何工作以及一些实际使用示例。

编辑:
创建 ViewModel 实例的方式也很重要。这就是一开始就导致您丢失 ViewModel 数据的细节。

让我们想象一下以下 ViewModel:

class MyViewModel() : ViewModel() {
    init {
        println("MyViewModel created")
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我们像这样实例化它:

MyComposableFuncion(
    viewModel: MyViewModel = MyViewModel()
)
Run Code Online (Sandbox Code Playgroud)

我们处理的不是一个会持续存在的实例。这是因为我们刚刚调用了 MyViewModel 实例,并且当屏幕旋转时将重复此操作,从而生成具有所有默认数据的此 ViewModel 的新实例。

如果我们这样做:

import androidx.lifecycle.viewmodel.compose.viewModel

MyComposableFuncion(
    viewModel: MyViewModel = viewModel()
)
Run Code Online (Sandbox Code Playgroud)

现在我们使用该viewModel()函数而不是传递MyViewModel(). viewModel()即使屏幕旋转,ViewModel 实例的持久性也在幕后进行处理。

如果 ViewModel 有参数,则需要创建一个ViewModelProvider.Factory提供必要参数的工厂,然后在 viewModel() 函数中传递该工厂。评论中提到的教程
也解释了所有这些信息:

之前我们使用 viewModel() 函数来创建视图模型。不幸的是,viewModel() 函数不允许我们在调用它时简单地传递 Application 引用作为参数。相反,我们需要向函数传递一个自定义 ViewModelProvider Factory 类,该类旨在接受 Application 引用并返回初始化的 MainViewModel 实例。

我的建议是使用一些库来注入依赖项,例如Hilt。但我相信,如果您刚刚开始学习,最重要的是遵循您选择的教程直到最后,这样您就不会混淆事情并变得复杂。