Android组合导航和ViewModel生命周期

Android Compose Navigation and ViewModel lifecycle(Android组合导航和ViewModel生命周期)
本文介绍了Android组合导航和ViewModel生命周期的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我才刚刚开始作曲。乍一看,对我来说,它看起来就像是我喜欢的SwiftUI的副本。但当我开始真正使用它时,我很快就遇到了很多问题。显然,我需要找到正确的方式来使用它以从中受益...

这是我的一个问题。

package org.test.android.kotlin.compose.ui

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import org.test.android.kotlin.compose.ui.theme.MbiKtTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MbiKtTheme {
                val navController = rememberNavController()
                // <Edit #1>
                // Navigator.route.collectAsState("").value.takeIf { it.isNotEmpty() }?.also { navController.navigate(it) }
                // Navigator.route.observe(this, { route -> navController.navigate(route) })
                // </Edit #1>
                // <Edit #2>
                Navigator.route.collectAsState("").value.takeIf { it.isNotEmpty() }?.also { 
                    navController.popBackStack()
                    navController.navigate(it)
                }
                // </Edit #2>
                Surface(color = MaterialTheme.colors.background) {
                    NavHost(
                        navController = navController,
                        startDestination = "setup"
                    ) {
                        composable(route = "setup") {
                            SetupScreen()
                        }
                        composable(route = "progress") {
                            ProgressScreen()
                        }
                    }
                }
            }
        }
    }
}

// This is unnecessary here in this simple code fragment, but a MUST for large modular projects
object Navigator {
    // <Edit #1>
    val route = MutableSharedFlow<String>(0, 1, BufferOverflow.DROP_OLDEST)
    //val route: MutableLiveData<String> = MutableLiveData()
    // </Edit #1>
}

class SetupViewModel : ViewModel() {
    init {
        Log.d(toString(), "Create")
    }

    override fun onCleared() {
        Log.d(toString(), "Destroy")
    }

    override fun toString(): String {
        return "SetupViewModel"
    }
}

@Composable
fun SetupScreen(model: SetupViewModel = viewModel()) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(all = Dp(8f))
    ) {
        Text(text = "Setup")
        Spacer(modifier = Modifier.weight(1f))
        Button(onClick = { Navigator.route.tryEmit("progress") }, modifier = Modifier.fillMaxWidth()) { Text(text = "Register") }
    }
}

class ProgressViewModel : ViewModel() {
    init {
        Log.d(toString(), "Created")
    }

    override fun onCleared() {
        Log.d(toString(), "Cleared")
    }

    override fun toString(): String {
        return "ProgressViewModel"
    }
}

@Composable
fun ProgressScreen(model: ProgressViewModel = viewModel()) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(all = Dp(8f))
    ) {
        Text(text = "Progress")
        Spacer(modifier = Modifier.weight(1f))
        Button(onClick = { Navigator.route.tryEmit("setup") }, modifier = Modifier.fillMaxWidth()) { Text(text = "Abort") }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MbiKtTheme {
        SetupScreen()
    }
}

我的现实当然要复杂得多,我尽了最大努力将它尽可能地简化,但这已经证明了我的问题:

  • 在两个屏幕(可合成的)之间导航并旋转屏幕
  • 并在日志中查看来自两个视图模型的已创建/已销毁消息
  • 首先:从一个屏幕导航到另一个屏幕时决不会调用Debled(显然是因为活动保持活动状态),这在大型项目中是完全不能接受的
  • 随后,只要您至少导航到另一个屏幕一次(只需轻触按钮),每次屏幕旋转就会开始重新创建视图模型,这也是完全不可接受的

我知道Compose还不成熟(我已经看到一些组件仍在Alpha&Quot;发行版中)。因此,这可能是作文本身存在错误。

或者这可能只是我对如何在大型和模块化项目中使用Compose的理解错误...

有什么想法吗?

(仅为完整起见,我再次确认我使用的是所有内容的当前最新版本。)

编辑#1(2021/09/05)

多亏了那篇关于我的一个问题的文章(下面评论中的链接),我修复了其中一个问题:在旋转屏幕时不再重新创建视图模型(仍然没有线索,原因是什么)。

因此,剩下的问题是视图模型没有遵循预期的生命周期。

编辑#2(2021/09/13)

多亏了下面的答案(不幸的是,我没有找到任何方法让它被接受-SF UI对我来说仍然有点不清楚),我能够真正使视图模型的生命周期按预期工作。

我刚刚禁用了后台堆栈,这在我的应用程序中无论如何都是不需要的(在UI和底层模型之间造成了很多混乱)功能...

推荐答案

每次旋转屏幕时都会重新生成完整的合成树,从setContent开始。

在源代码中,您在每次重新组合时都订阅Navigator.route.observe。而&修复&则是将LiveDataFloat转换为复合状态。您使用Flow+collectAsState实现了这一点,对于LiveData,一个类似的方法称为observeAsState。了解有关state in compose的更多信息。

所以,每次您旋转设备时,navigate都会被调用。

navigate不会使用新目标更改当前屏幕。相反,它会将新视图推送到堆栈上。因此,每次navigate,您都会将一个新屏幕推到导航堆栈上,并为其创建一个模型。当您在没有collectAsState的情况下旋转设备时,您会将另一个屏幕推送到堆栈上。在documentation中查看有关撰写导航的更多信息。

您可以使用NavOptionsBuilder更改此行为,例如:

navController.navigate(route) {
    if (route == "setup") {
        popUpTo("setup")
    }
}

当相应的视图离开导航堆栈时,将释放视图模型。如果单击导航栏上的"上一步"按钮,您将看到它已被释放。

附注:我个人发现Compose比SwiftUI更灵活、更方便,尽管第一个稳定的版本一个月前才发布。你只需要更好地了解它。

这篇关于Android组合导航和ViewModel生命周期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本站部分内容来源互联网,如果有图片或者内容侵犯您的权益请联系我们删除!

相关文档推荐

Looking to understand the iOS UIViewController lifecycle(希望了解 iOS UIViewController 生命周期)
how to fix unresolved reference lifecycleScope?(如何修复未解析的引用生命周期clScope?)
ActionBarSherlock with ViewPager not calling ViewPager fragments lifecycle methods when going to and returning from backstack(带有 ViewPager 的 ActionBarSherlock 在返回堆栈和返回堆栈时不调用 ViewPager 片段生命周期方法)
Android Lifecycle management of Fragments within ViewPager and FragmentPagerAdapter(ViewPager 和 FragmentPagerAdapter 中 Fragments 的 Android 生命周期管理)
CoreBluetooth: What is the lifetime of unique UUIDs(CoreBluetooth:唯一 UUID 的生命周期是多少)