如何在依赖于视图模型的可组合函数中获得预览?
How to get preview in composable functions that depend on a view model?
问题描述
我想在我的 HomeScreenPrevieiw
预览函数中预览我的 HomeScreen
可组合函数。但是这是不可能的,因为我收到以下错误:
java.lang.IllegalStateException: ViewModels creation is not supported in Preview
at androidx.compose.ui.tooling.ComposeViewAdapter$FakeViewModelStoreOwner.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)
...
我的代码
这是我的 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) }
}
}
这是我的预览功能的代码:
@Preview
@Composable
private fun HomeScreenPreview() {
HomeScreen(navigateToDetailsAction = {}, openCardDetailsAction = {})
}
我的视图模型:
@HiltViewModel
class HomeViewModel @Inject constructor(repository: CityRepository) : ViewModel() {
val cities: LiveData<List<City>> = repository.allCities.asLiveData()
}
存储库:
@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)
}
应用程序数据库:
@Database(entities = [City::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun getCityDao() : CityDao
}
我失败的尝试
我认为作为我的 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) }
}
}
但它仍然不起作用(我一直收到同样的错误),而且它不利于测试,因为它会阻止我使用模拟视图模型测试我的 HomeScreen
。
这正是使用默认值传递视图模型的原因之一。在预览中,可以传递一个测试对象:
@Preview
@Composable
private fun HomeScreenPreview() {
val viewModel = HomeViewModel()
// setup viewModel as you need it to be in the preview
HomeScreen(viewModel = viewModel, navigateToDetailsAction = {}, openCardDetailsAction = {})
}
因为你有一个存储库,你可以做同样的事情来测试视图模型。
- 为
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)
}
- 为
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)
}
- 创建
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 }
}
所以你可以将它传递到你的视图模型中:HomeViewModel(FakeCityRepository())
您可以使用 AppDatabase
而不是存储库来执行相同的操作,这完全取决于您的需要。查看更多关于 Hilt testing
p.s。我不确定这是否会构建,因为我没有你的一些 类,但你应该已经明白了。
您好,@Philip Dukhov 在他的回答中解释的是正确的,理想情况下应该这样做。
但我想提出一个解决方法,因为它需要大量设置,例如伪造和手动创建中间对象。
您可以通过使用自定义 运行 配置并使用特定 Activity 作为带有 @AndroidEntryPoint 注释的预览Activity 来在模拟器上运行预览。
您可以按照来自博客的屏幕截图和内部结构的详细指南进行操作 我已经从 here
发布了
或者您可以
Activity 需要
@AndroidEntryPoint
class HiltPreviewActivity : AppCompatActivity() {
....
}
您需要手动将预览可组合项复制粘贴到 HiltPreviewsetContent{..}
中Activity。
运行 从工具栏,而不是从预览快捷方式,查看模式详细信息指南。
问题描述
我想在我的 HomeScreenPrevieiw
预览函数中预览我的 HomeScreen
可组合函数。但是这是不可能的,因为我收到以下错误:
java.lang.IllegalStateException: ViewModels creation is not supported in Preview
at androidx.compose.ui.tooling.ComposeViewAdapter$FakeViewModelStoreOwner.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)
...
我的代码
这是我的 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) }
}
}
这是我的预览功能的代码:
@Preview
@Composable
private fun HomeScreenPreview() {
HomeScreen(navigateToDetailsAction = {}, openCardDetailsAction = {})
}
我的视图模型:
@HiltViewModel
class HomeViewModel @Inject constructor(repository: CityRepository) : ViewModel() {
val cities: LiveData<List<City>> = repository.allCities.asLiveData()
}
存储库:
@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)
}
应用程序数据库:
@Database(entities = [City::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun getCityDao() : CityDao
}
我失败的尝试
我认为作为我的 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) }
}
}
但它仍然不起作用(我一直收到同样的错误),而且它不利于测试,因为它会阻止我使用模拟视图模型测试我的 HomeScreen
。
这正是使用默认值传递视图模型的原因之一。在预览中,可以传递一个测试对象:
@Preview
@Composable
private fun HomeScreenPreview() {
val viewModel = HomeViewModel()
// setup viewModel as you need it to be in the preview
HomeScreen(viewModel = viewModel, navigateToDetailsAction = {}, openCardDetailsAction = {})
}
因为你有一个存储库,你可以做同样的事情来测试视图模型。
- 为
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)
}
- 为
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)
}
- 创建
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 }
}
所以你可以将它传递到你的视图模型中:HomeViewModel(FakeCityRepository())
您可以使用 AppDatabase
而不是存储库来执行相同的操作,这完全取决于您的需要。查看更多关于 Hilt testing
p.s。我不确定这是否会构建,因为我没有你的一些 类,但你应该已经明白了。
您好,@Philip Dukhov 在他的回答中解释的是正确的,理想情况下应该这样做。
但我想提出一个解决方法,因为它需要大量设置,例如伪造和手动创建中间对象。
您可以通过使用自定义 运行 配置并使用特定 Activity 作为带有 @AndroidEntryPoint 注释的预览Activity 来在模拟器上运行预览。
您可以按照来自博客的屏幕截图和内部结构的详细指南进行操作 我已经从 here
发布了或者您可以
Activity 需要
@AndroidEntryPoint
class HiltPreviewActivity : AppCompatActivity() {
....
}
您需要手动将预览可组合项复制粘贴到 HiltPreviewsetContent{..}
中Activity。
运行 从工具栏,而不是从预览快捷方式,查看模式详细信息指南。