Jetpack Compose - NavHost 预览问题

Posted

技术标签:

【中文标题】Jetpack Compose - NavHost 预览问题【英文标题】:Jetpack Compose - NavHost preview problem 【发布时间】:2021-10-13 13:44:55 【问题描述】:

我今天开始学习jetpack compose,使用NavHost时出现渲染预览问题:

java.lang.IllegalStateException: ViewModels creation is not supported in Preview
at androidx.compose.ui.tooling.ComposeViewAdapter$FakeViewModelStoreOwner$1.getViewModelStore(ComposeViewAdapter.kt:709)
at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:103)
at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:66)
at com.example.jetpackstudy.ui.activity.BottomNavActivity$AppContentView$1$3$1.invoke(BottomNavActivity.kt:71)
at com.example.jetpackstudy.ui.activity.BottomNavActivity$AppContentView$1$3$1.invoke(BottomNavActivity.kt:70)

我的项目代码:

@Preview @Composable
fun AppContentView() 
    JetPackStudyTheme 
        val navController = rememberNavController()
        Scaffold(topBar = ...)
        , bottomBar = ...
        ) 
            Surface(color = MaterialTheme.colors.primary, modifier = Modifier.fillMaxSize()) 
                NavHost(navController, startDestination = BotNavItem.Home.route) 
                    ...
                
            
        
    

当我使用NavHost 时,有什么方法可以修复 Android Studio 上的预览?我正在使用依赖项:

implementation "androidx.navigation:navigation-compose:2.4.0-alpha06"

【问题讨论】:

【参考方案1】:

您可以使用的一种技术是为您的屏幕设置一个包装器,您的包装器将从您的视图模型中获取状态并将其传递给实际屏幕。然后,您可以预览获取状态的屏幕,而不是视图模型。像这样的

@Composable
fun CityScreen(
    viewModel: CityViewModel,
    modifier: Modifier = Modifier,
) 
    val state = viewModel.state.collectAsState()
    CityScreen(
        state = state.value,
        modifier = modifier,
    )


@Composable
private fun CityScreen(
    state: CityState,
    modifier: Modifier = Modifier,
) 
    // code here

【讨论】:

我不认为预览 NavHost 是一个可行的选择,或者事实上,即使是单个屏幕。预览适用于部分产品。如果您希望以形式查看整个屏幕,最好实际部署它。因此,名称。 NavHost 甚至不应该作为参数传递,我的 sn-p 显示传递视图模型,然后传递状态。如果您需要导航传递一个 lambda 来执行此操作。并且预览全屏非常有效,您可能希望同时查看它如何适应不同的外形尺寸,而无需部署多个模拟器或设备。 好吧,那好吧。感谢您的信息:) 不幸的是,这个答案不起作用。我遇到了同样的问题,但即使我从 compose 函数中删除了对视图模型的所有引用,NavHost 的存在也会导致出现此错误。有可能在构建导航图的时候,NavHost构造函数需要构建一些ViewModelshelper【参考方案2】:

我找到了一种可能对某人有所帮助的解决方法。很明显,NavHost(...) 内部的某些东西正在调用一些在预览模拟器中被禁止的视图模型函数。因此,我们只在运行时实例化时提供NavHost,并在预览期间替换其中一个导航目的地。

fun TheComponent(viewModel: YourViewModel, preview: Boolean = false) 
    ...
    if (preview) 
        AFragment(...)
     else 
        NavHost(navController, "destination") 
           composable("destination")  AFragment(...) 
           ...
        
    
    ...

如果需要,您可以在预览中注入模拟视图模型。重要的是在呈现预览时不会调用NavHost(...)。只需将标志传递给您的组件,以便它替换预览

@Preview()
@Composable
fun ThePreview() 
    val viewModel = AViewModel(mocks...)
    TheComponent(viewModel, true)

理想情况下,NavHost() 在预览中运行时会调用 startDestination 组件的组合函数,而不是构建导航图,然后可以删除此样板。

【讨论】:

【参考方案3】:

您实际上可以通过替换导航主机控制器值来解决此问题。您可以随意预览顶栏、底栏和中间内容,只要它填写了预览代码,而不是来自导航主机。

顶部布局(未预览!!):

/**
 * Top main activity layout with val's that trigger recomposition for everything.
 */
@Composable
fun MainActivityTopLayout() 
    val navController = rememberNavController()
    val viewModel: MainActivityViewModel = hiltViewModel()
    // Add your variables here

    MainActivityLayout(
        navController = navController,
        // Pass your variables here
    )

预览布局:

/**
 * Call this from a compose function that stores the viewmodel, 
 * if you have any. Since previews can't instantiate ViewModels,
 * you should add everything you need from the viewmodel as
 * function parameter.
**/
@Composable
fun MainActivityLayout(
    content: (@Composable (PaddingValues) -> Unit)? = null,
    navController: NavHostController,
    // ...
) 
    Scaffold(
        // Top and bottom bar declarations here...
        // mainNavHost() is a function that returns your NavHost.
        content = content ?: mainNavHost(navController)
    )

您的预览将如下所示:

/**
 * Previews the main activity layout.
 * Note that providing an empty block for padding values fixes
 * the compose preview.
 */
@Preview(showBackground = true)
@Composable
fun PreviewMainActivityLayout() 
    AppTheme 
        MainActivityLayout(
            content = 
                // Preview your content, ie. MyOtherView(...)
            ,
            navController = rememberNavController(),
            // ...
        )
    

预览愉快。如果对此还有任何疑问,请告诉我!

【讨论】:

以上是关于Jetpack Compose - NavHost 预览问题的主要内容,如果未能解决你的问题,请参考以下文章

如何在jetpack compose中定义不同的屏幕

jetpack compose 接收返回参数

Jetpack Compose 中的作用域状态

Jetpack Compose 的 Navigation学习

Jetpack Compose 的 Navigation学习

Jetpack Compose 的 Navigation学习