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

Pie*_*ira 12 android android-viewmodel android-jetpack-compose

问题描述

HomeScreen我想在预览功能中预览可组合功能HomeScreenPrevieiw。但是这是不可能的,因为我收到以下错误:

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)
    ...
Run Code Online (Sandbox Code Playgroud)

我的代码

这是我的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) }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的预览功能的代码:

@Preview
@Composable
private fun HomeScreenPreview() {
    HomeScreen(navigateToDetailsAction = {}, openCardDetailsAction = {})
}
Run Code Online (Sandbox Code Playgroud)

我的视图模型:

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

存储库:

@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)

}
Run Code Online (Sandbox Code Playgroud)

应用程序数据库:

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

我失败的尝试

我认为这可能是视图模型作为我的默认参数传递的问题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) }
    }
}
Run Code Online (Sandbox Code Playgroud)

但它仍然不起作用(我不断收到相同的错误),并且它不利于测试,因为它会阻止我HomeScreen使用模拟视图模型进行测试。

Phi*_*hov 10

这正是视图模型传递默认值的原因之一。在预览中,您可以传递一个测试对象:

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

由于您有一个存储库,因此您可以执行与测试视图模型相同的操作。

  1. 创建接口用​​于CityRepository
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)
}
Run Code Online (Sandbox Code Playgroud)
  1. 实施它的目的是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)
}
Run Code Online (Sandbox Code Playgroud)
  1. 创建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 }
}
Run Code Online (Sandbox Code Playgroud)

所以你可以将它传递到你的视图模型中:HomeViewModel(FakeCityRepository())

您可以使用存储库而不是存储库执行相同的操作AppDatabase,这完全取决于您的需求。查看有关Hilt 测试的更多信息

ps 我不确定这是否会建立,因为我没有你的一些课程,但你应该已经明白了这个想法。

  • @Dr.jacky 正如我在回答中所说,从预览中你应该传递 `FakeCityRepository()` 而不是调用 `hiltViewModel` (4认同)