[英]How to get preview in composable functions that depend on a view model?
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)
...
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
}
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.由于您有一个存储库,您可以执行与测试视图模型相同的操作。
CityRepository
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
: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
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.