android 在更改选项卡时,将NavigationBottomItem保存在内存中,而不是在Jetpack编写导航中销毁它

bpzcxfmw  于 2023-06-04  发布在  Android
关注(0)|答案(2)|浏览(202)

bounty还有4天到期。回答此问题可获得+100声望奖励。J. Doe正在寻找一个答案从一个有信誉的来源

后台

我正在创建一个聊天应用程序,当用户离开聊天时,我需要执行逻辑(聊天只是一个@Composable fun,我正在使用LocalLifecycleOwner.current与ViewModel的组合,它监视onDestroy方法以取消订阅用户)。既然当用户更改选项卡时也会执行逻辑,那么这种情况就不应该发生了。

问题

我用的是ScaffoldBottomNavigation。当我切换选项卡时,旧的选项卡将被销毁。我不希望出现这种情况,旧选项卡应该保留在内存中。remember块在返回选项卡时也会重新执行,我不希望这样。我应该使用多个导航主机还是什么?

目标

在没有重新执行remember块的情况下在选项卡之间导航(并且LocalLifecycleOwner.current不应该发布onDestroy)。

示例代码

整个项目可以在这里找到:https://github.com/Jasperav/JetpackComposeNavigation。您可以看到,当切换选项卡时,remember块被重新执行,VM被销毁(参见日志记录)。我不想要这种行为,它应该被保存在内存中。这是相关代码:

@Composable
fun Screen() {
    val items = listOf(
        Triple("a", Icons.Default.Person, Icons.Filled.Person),
        Triple("b", Icons.Default.Notifications, Icons.Filled.Notifications),
    )
    var selectedTab = items[0]
    val navHostController = rememberNavController()

    Scaffold(
        bottomBar = {
            BottomNavigation {
                items.forEachIndexed { index, item ->
                    val isSelected = index == items.indexOf(selectedTab)

                    BottomNavigationItem(
                        icon = { Icon(if (isSelected) item.second else item.third, contentDescription = null) },
                        label = { Text(text = item.first) },
                        selected = isSelected,
                        onClick = {
                            navHostController.navigate(item.first) {
                                popUpTo(navHostController.graph.findStartDestination().id)

                                launchSingleTop = true
                            }
                        }
                    )
                }
            }
        }
    ) {
        NavHost(
            navHostController,
            startDestination = items[0].first,
            Modifier.padding(it)
        ) {
            composable(items[0].first) {
                selectedTab = items[0]

                val lifecycle = LocalLifecycleOwner.current
                val viewModel: ModelDontDestory = viewModel(factory = viewModelFactory {
                    ModelDontDestory(lifecycle)
                })

                remember {
                    println("Recomposed first")

                    ""
                }

                Text("first")
            }
            composable(items[1].first) {
                selectedTab = items[1]

                val lifecycle = LocalLifecycleOwner.current
                val viewModel: ModelDontDestory = viewModel(factory = viewModelFactory {
                    ModelDontDestory(lifecycle)
                })

                remember {
                    println("Recomposed second")

                    ""
                }

                Text("Second")
            }
        }
    }
}

inline fun <VM : ViewModel> viewModelFactory(crossinline f: () -> VM) =
    object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(aClass: Class<T>): T = f() as T
    }

class ModelDontDestory(val lifecycle: LifecycleOwner): ViewModel(), DefaultLifecycleObserver {
    init {
        lifecycle.lifecycle.addObserver(this)
    }

    override fun onDestroy(owner: LifecycleOwner) {
        super.onDestroy(owner)

        println("This should never happen, this should be kept in memory")
    }
}
c3frrgcw

c3frrgcw1#

您已经有一个对象保留在内存中-ViewModel示例本身。您不应该查看Lifecycle示例的销毁,因为当您的应用程序经历配置更改时也会发生这种情况。
相反,您应该查看ViewModel的onCleared方法--只有当示例实际从回退堆栈中删除时(例如,用户离开聊天),才会调用该方法。
类似地,您不应该将remember用于需要保存的状态(同样,remember变量会由于多种原因而被擦除,包括在您进行配置更改时)。对于需要在屏幕保留在返回堆栈上的整个时间内保留的值,应该使用rememberSaveable

zbq4xfa0

zbq4xfa02#

根据我对“Lifecycle of composables”的理解以及Ziv Kesten的“Implement Bottom Bar Navigation in Jetpack Compose”示例,remember块(explained here)和生命周期事件与@Composable函数的生命周期有着内在的联系。当您离开屏幕时,它的@Composable函数将被丢弃,当您返回时,它们将被重新组合。

在Jetpack Compose中,remember函数用于在重组过程中保留状态。
但是,使用remember保存的状态无法在配置更改或进程死亡后继续存在。如果您需要在配置更改或进程死亡时保留状态,则应该使用rememberSaveable函数。
要使用rememberSaveable,您需要确保您保存的数据是可序列化的,因为它需要放入Bundle
如:

@Composable
fun MyComposable() {
    val myState = rememberSaveable { mutableStateOf(MyState()) }

    // Use myState in your composable
}

MyState需要是一个属性都是可序列化的数据类。rememberSaveable函数确保在配置更改和进程死亡时保存和恢复状态。
然而,在您提供的代码中,似乎您正在使用remember来在重新组合可组合对象时触发printlnremember(和rememberSaveable)通常不用于此目的。相反,rememberrememberSaveable用于在重组或配置更改时保留状态。
如果您只想在重新组合组合时打印日志消息,则可以直接使用println,而不使用remember
如果你想在内存中保存一些状态,你可以同时使用ViewModelrememberSaveable,其中rememberSaveable保存UI状态,ViewModel保存更持久的数据状态。
这意味着这将是一个更简单的实现:

@Composable
fun Screen() {
    val items = listOf(
        Triple("a", Icons.Default.Person, Icons.Filled.Person),
        Triple("b", Icons.Default.Notifications, Icons.Filled.Notifications),
    )
    // Use rememberSaveable to persist the selected tab across configuration changes.
    val selectedTabIndex = rememberSaveable { mutableStateOf(0) }
    val navHostController = rememberNavController()

    Scaffold(
        bottomBar = {
            BottomNavigation {
                items.forEachIndexed { index, item ->
                    val isSelected = index == selectedTabIndex.value

                    BottomNavigationItem(
                        icon = { Icon(if (isSelected) item.second else item.third, contentDescription = null) },
                        label = { Text(text = item.first) },
                        selected = isSelected,
                        onClick = {
                            // Update the selected tab index when a tab is clicked.
                            selectedTabIndex.value = index
                            navHostController.navigate(item.first) {
                                popUpTo(navHostController.graph.findStartDestination().id)
                                launchSingleTop = true
                            }
                        }
                    )
                }
            }
        }
    ) {
        NavHost(
            navHostController,
            startDestination = items[0].first,
            Modifier.padding(it)
        ) {
            composable(items[0].first) {
                val viewModel: ModelDontDestroy = viewModel()

                Text("first")
            }
            composable(items[1].first) {
                val viewModel: ModelDontDestroy = viewModel()

                Text("Second")
            }
        }
    }
}

class ModelDontDestroy: ViewModel() {
    // Override onCleared to perform clean-up when this ViewModel is no longer needed.
    override fun onCleared() {
        super.onCleared()

        println("This should never happen, this should be kept in memory")
    }
}

随着变化:
1.已从ViewModel中删除LifecycleOwnerViewModel不需要引用LifecycleOwner。相反,当不再需要ViewModel时,应该使用ViewModelonCleared方法来执行清理。
1.已将所选选项卡索引的remember更改为rememberSaveable。这确保了即使发生配置更改(例如,屏幕旋转)时也能记住所选选项卡。
1.删除了在重新组合组合组件时用于打印日志消息的remember块。如果您需要在重新组合Composable时执行某个操作,则可以直接在Composable中调用该操作。remember函数不需要这样做。

相关问题