【问题标题】:Jetpack Compose - NavHost preview problemJetpack Compose - NavHost 预览问题
【发布时间】: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"

【问题讨论】:

    标签: kotlin android-jetpack-compose


    【解决方案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(),
                  // ...
              )
          }
      }
      

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

      【讨论】:

        猜你喜欢
        • 2022-11-12
        • 1970-01-01
        • 1970-01-01
        • 2021-04-26
        • 2021-10-05
        • 1970-01-01
        • 2022-10-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多