简体   繁体   English

如何在依赖于视图模型的可组合函数中获得预览?

[英]How to get preview in composable functions that depend on a view model?

Problem description问题描述

I would like to have the preview of my HomeScreen composable function in my HomeScreenPrevieiw preview function.我想在我的HomeScreenPrevieiw预览功能中预览我的HomeScreen可组合功能。 However this is not being possible to do because I am getting the following error:但是,这是不可能的,因为我收到以下错误:

java.lang.IllegalStateException: ViewModels creation is not supported in Preview
    at androidx.compose.ui.tooling.ComposeViewAdapter$FakeViewModelStoreOwner$1.getViewModelStore(ComposeViewAdapter.kt:709)
    at androidx.lifecycle.ViewModelProvider.<init>(ViewModelProvider.kt:105)
    at androidx.lifecycle.viewmodel.compose.ViewModelKt.get(ViewModel.kt:82)
    at androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(ViewModel.kt:72)
    at com.example.crud.ui.screens.home.HomeScreenKt.HomeScreen(HomeScreen.kt:53)
    at com.example.crud.ui.screens.home.HomeScreenKt.HomeScreenPreview(HomeScreen.kt:43)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    ...

My code我的代码

This is my HomeScreen code:这是我的主HomeScreen代码:

@Composable
fun HomeScreen(
    viewModel: HomeViewModel = hiltViewModel(),
    navigateToDetailsAction: () -> Unit,
    openCardDetailsAction: (Int) -> Unit
) {
    val cities = viewModel.cities.observeAsState(listOf())
    Scaffold(
        topBar = { HomeAppBar() },
        floatingActionButton = { HomeFab(navigateToDetailsAction) }
    ) {
        HomeContent(cities) { id -> openCardDetailsAction(id) }
    }
}

This is the code for my preview function:这是我的预览功能的代码:

@Preview
@Composable
private fun HomeScreenPreview() {
    HomeScreen(navigateToDetailsAction = {}, openCardDetailsAction = {})
}

My view model:我的视图模型:

@HiltViewModel
class HomeViewModel @Inject constructor(repository: CityRepository) : ViewModel() {
    val cities: LiveData<List<City>> = repository.allCities.asLiveData()
}

Repository:存储库:

@ViewModelScoped
class CityRepository @Inject constructor(appDatabase: AppDatabase) {
    private val dao by lazy { appDatabase.getCityDao() }

    val allCities by lazy { dao.getAllCities() }

    suspend fun addCity(city: City) = dao.insert(city)

    suspend fun updateCity(city: City) = dao.update(city)

    suspend fun deleteCity(city: City) = dao.delete(city)

    suspend fun getCityById(id: Int) = dao.getCityById(id)

}

AppDatabase:应用数据库:

@Database(entities = [City::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
    abstract fun getCityDao() : CityDao
}

My failed attempt我失败的尝试

I thought it might be a problem with the view model being passed as the default parameter of my HomeScreen and so I decided to do it this way:我认为将视图模型作为我的HomeScreen的默认参数传递可能是一个问题,所以我决定这样做:

@Composable
fun HomeScreen(
    navigateToDetailsAction: () -> Unit,
    openCardDetailsAction: (Int) -> Unit
) {
    val viewModel: HomeViewModel = hiltViewModel()
    val cities = viewModel.cities.observeAsState(listOf())
    Scaffold(
        topBar = { HomeAppBar() },
        floatingActionButton = { HomeFab(navigateToDetailsAction) }
    ) {
        HomeContent(cities) { id -> openCardDetailsAction(id) }
    }
}

But it still doesn't work (I keep getting the same error), and it's not good for testing as it would prevent me from testing my HomeScreen with a mocked view model.但它仍然不起作用(我不断收到相同的错误),并且不利于测试,因为它会阻止我使用模拟视图模型测试我的HomeScreen

This is exactly one of the reasons why the view model is passed with a default value.这正是视图模型传递默认值的原因之一。 In the preview, you can pass a test object:在预览中,您可以传递一个测试对象:

@Preview
@Composable
private fun HomeScreenPreview() {
    val viewModel = HomeViewModel()
    // setup viewModel as you need it to be in the preview
    HomeScreen(viewModel = viewModel, navigateToDetailsAction = {}, openCardDetailsAction = {})
}

Since you have a repository, you can do the same thing you would do to test the view model.由于您有一个存储库,您可以执行与测试视图模型相同的操作。

  1. Create interface for CityRepositoryCityRepository创建接口
interface CityRepositoryI {
    val allCities: List<City>

    suspend fun addCity(city: City)
    suspend fun updateCity(city: City)
    suspend fun deleteCity(city: City)
    suspend fun getCityById(id: Int)
}
  1. Implement it for CityRepository :CityRepository实现它:
@ViewModelScoped
class CityRepository @Inject constructor(appDatabase: AppDatabase) : CityRepositoryI {
    private val dao by lazy { appDatabase.getCityDao() }

    override val allCities by lazy { dao.getAllCities() }

    override suspend fun addCity(city: City) = dao.insert(city)

    override suspend fun updateCity(city: City) = dao.update(city)

    override suspend fun deleteCity(city: City) = dao.delete(city)

    override suspend fun getCityById(id: Int) = dao.getCityById(id)
}
  1. Create FakeCityRepository for testing purposes:创建FakeCityRepository用于测试目的:
class FakeCityRepository : CityRepositoryI {
    // predefined cities for testing
    val cities = listOf(
        City(1)
    ).toMutableStateList()

    override val allCities by lazy { cities }

    override suspend fun addCity(city: City) {
        cities.add(city)
    }

    override suspend fun updateCity(city: City){
        val index = cities.indexOfFirst { it.id == city.id }
        cities[index] = city
    }

    override suspend fun deleteCity(city: City) {
        cities.removeAll { it.id == city.id }
    }

    override suspend fun getCityById(id: Int) = cities.first { it.id == id }
}

So you can pass it into your view model: HomeViewModel(FakeCityRepository())所以你可以将它传递到你的视图模型中: HomeViewModel(FakeCityRepository())

You can do the same with AppDatabase instead of a repository, it all depends on your needs.你可以用AppDatabase而不是存储库来做同样的AppDatabase ,这完全取决于你的需要。 Check out more about Hilt testing查看有关Hilt 测试的更多信息

ps I'm not sure if this will build, since I don't have some of your classes, but you should have caught the idea. ps我不确定这是否会建立,因为我没有你的一些课程,但你应该明白这个想法。

Hi like @Philip Dukhov has explained in his answer is correct and ideally should be done this way.嗨,@Philip Dukhov 在他的回答中解释说是正确的,理想情况下应该这样做。

But I would like to suggest a workaround cause it requires a lot of setups like fakes and manually creating intermediate objects.但我想建议一个解决方法,因为它需要很多设置,比如假货和手动创建中间对象。

You can get your preview working on the emulator by using a custom run configuration and using a specific Activity as PreviewActivity with @AndroidEntryPoint Annotation.您可以通过使用自定义运行配置并使用特定 Activity 作为 PreviewActivity 和 @AndroidEntryPoint Annotation 来在模拟器上运行预览。

You can follow a detailed guide with screen shot and internals from the blog I have published from here您可以按照此处发布的博客中的屏幕截图和内部结构的详细指南进行操作

Or simply you can或者干脆你可以

在此处输入图片说明

在此处输入图片说明

在此处输入图片说明

Activity Needs to have活动需要有

@AndroidEntryPoint
class HiltPreviewActivity : AppCompatActivity() {
....
}

you need to manually copy-paste preview composable into setContent{..} of the HiltPreviewActivity.您需要手动将可组合的预览复制粘贴到 HiltPreviewActivity 的setContent{..}中。

Run from the toolbar, not from preview shortcut, check guide for mode details.从工具栏运行,而不是从预览快捷方式,检查模式详细信息指南。

在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何将可组合预览绘制到 HTML 文件 - How to draw Composable Preview to HTML file 在可组合函数中重复使用 Jetpack Compose 中的一组预览注释 - Reuse a single set of preview annotations in Jetpack Compose across composable functions Android View 与可组合项相比如何 - HOW Android View compares to a composable Android Jetpack Compose 从 Composable 内的 Fragment 获取活动视图模型 - Android Jetpack Compose get Activity View Model from Fragment inside Composable 如何预览具有非空类型参数的可组合项? - How to preview a Composable with non-null type parameters? 如何在 Android Jetpack Compose 中禁用可组合预览? - How do I disable the composable preview in Android Jetpack Compose? 为什么当 state 在视图 model 中更改时,路由的 Composable 会重新组合? - Why does the Composable for the route recompose when state changes in the view model? 预览中不支持具有非默认参数的可组合函数,除非它们使用 @PreviewParameter 注释 - Composable functions with non-default parameters are not supported in Preview unless they are annotated with @PreviewParameter 你能给我一些示例代码来证明从不依赖执行可组合函数的副作用吗? - Could you give me some sample code to prove Never depend on side-effects from executing composable functions? 如何在可组合 function 中获取 rootView? - How to get the rootView inside composable function?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM