Android组合导航和ViewModel生命周期 [英] Android Compose Navigation and ViewModel lifecycle

查看:0
本文介绍了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生命周期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆