How to get preview in composable functions that depend on a view model?
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.
- Create interface for
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)
}
- Implement it for
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)
}
- Create
FakeCityRepository
for testing purposes:
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())
You can do the same with AppDatabase
instead of a repository, it all depends on your needs. Check out more about Hilt testing
p.s. I'm not sure if this will build, since I don't have some of your classes, but you should have caught the idea.
How to make a @Preview in JetpackCompose, when the component depends of some data provide by ViewModel
I'm not sure of the depth of this application, but a potential idea would be to code to an interface and not an implementation.
That is, create an interface with all of the functions you need (that may already exist in your ViewModel), have your PokemonListViewModel
implement it, and create another mock class that implements it as well. Pass the mock into your preview and leave the real implementation with PokemonListViewModel
interface PokeListViewModel {
...
// your other val's
val isLoading: Boolean
fun searchPokemonList(pokemon: String)
fun loadPokemonPaginated()
// your other functions
...
}
Once you create your interface you can simply update your composables to be expecting an object that "is a" PokeListViewModel
, for example.
Hopefully this helps
How to save data in a composable function with view model constructor
It seems like I need to save the view model as a state or something?
You don't have to. ViewModel
s are already preserved as part of their owner scope. The same ViewModel
instance will be returned to you if you retrieve the ViewModel
s correctly.
I seem to be missing something.
You are initializing a new instance of your NFTViewModel
every time the navigation composable recomposes (gets called) instead of retrieving the NFTViewModel
from its ViewModelStoreOwner
.
You should retrieve ViewModel
s by calling viewModel()
or if you are using Hilt and @HiltViewModel
then call hiltViewModel()
instead.
No Hilt
val vm: NFTViewModel = viewModel()
Returns an existing
ViewModel
or creates a new one in the given owner (usually, a fragment or an activity), defaulting to the owner provided byLocalViewModelStoreOwner
.
The createdViewModel
is associated with the givenviewModelStoreOwner
and will be retained as long as the owner is alive (e.g. if it is an activity, until it is finished or process is killed).
If using Hilt (i.e. your ViewModel
has the @HiltViewModel
annotation)
val vm: NFTViewModel = hiltViewModel()
Returns an existing
@HiltViewModel
-annotatedViewModel
or creates a new one scoped to the current navigation graph present on theNavController
back stack.
If no navigation graph is currently present then the current scope will be used, usually, a fragment or an activity.
The above will preserve your view model state, however you are still resetting the state inside your composable if your composable exits the composition and then re-enters it at a later time, which happens every time you navigate to a different screen (to a different "screen" composable, if it is just a dialog then the previous composable won't leave the composition, because it will be still displayed in the background).
Due to this part of the code
@Composable
fun HomeScreen(viewModel: NFTViewModel) {
val feedState by viewModel.nftResponse.observeAsState()
// this resets to false when HomeScreen leaves and later
// re-enters the composition
val fetched = remember { mutableStateOf(false) }
if (!fetched.value) {
fetched.value = true
viewModel.getFeed()
}
fetched
will always be false when you navigate to (and back to) HomeScreen
and thus getFeed()
will be called.
If you don't want to call getFeed()
when you navigate back to HomeScreen
you have to store the fetched
value somewhere else, probably inside your NFTViewModel
and only reset it to false
when you want that getFeed()
is called again.
Jetpack compose preview crashes with hiltViewModel()
After some searching and reviewing of google sample codes here is the way you must follow to prevent the preview crashes:
Have a root composable for your screen and initial view model there, collect your data from viewModel as state (in case your data is flow) then pass data to content composable
@Composable
fun HomeScreen(
navController: NavController
){
val viewModel = hiltViewModel<HomeViewModel>()
val someData by viewModel.someData.collectAsState()
}
@Composable
fun HomeScreenContent(
someData: Type = defaultvalue
){
//your UI codes
}
in preview use, HomeScreenContent instead of HomeScreen, and your problem will be solved!
thanks to @PhilipDukhov that helped in the comments
Trying to get ViewModel in @Composable
The problem is you do not use State value in your view model. State is needed in order to register your value for Composable recomposition (in case the value is assigned/changed)
So You need to have View Model like this (I guess your fetchData is a suspend function):
class DataViewModel: ViewModel() {
//the list of live data
val listDataState = MutableState<List<Data>> = mutableStateOf(emptyList<Data>())
//initialize the viewmodel
init {
viewModelScope.launch {
val data = fetchData()
listDataState.value = data
}
}
suspend fun fetchData() : List<Data>{
//something like:
return dataRepository.getData()
}
}
Then in your @Composable function you get the data like this:
val viewModel: DataViewModel = viewModel()
val data = viewModel.listDataState.value
Jetpack compose check if a function has been run in preview mode
I usually do this by calling LocalInspectionMode.current
to know if I'm in preview mode. It returns true
if in preview mode.
Here's what the documentation says about LocalInspectionMode
:
Inspectable mode CompositionLocal. True if the composition is composed inside an Inspectable component.
Jetpack compose live preview
If your Preview never show after add @Preview
annotation in JetnewsApp
class. In my case after adding @Preview
i close project and then open project again it's working fine for me in Macbook pro.
Related Topics
CSS Media Queries on Nexus 7, Display Resolution Not Working in Code
How to Have a Masked Numeric Input Field
Sqlitedatabase Close() Function Causing Nullpointerexception When Multiple Threads
Google Maps API V2 Class Not Found
Android Back Button on a Progressive Web Application Closes De App
Borders on Certain Zoom Levels on Webpage
What Are Best Practices for Using Aes Encryption in Android
Overriding the Power Button in Android
How to Get Canonical Id from Gcm
Splash Screen While Loading a Url in a Webview in Android App
Android: I Want Use My Custom Font with Webviewb
Android: Starting an Activity for a Different Third Party App
Captured Photo Orientation Is Changing in Android
How to Record Audio on Webpage (iOS, Android, Pc/Mac) - No Flash
How to Add Firebase Database Rules Without Authentication
Get Android .Apk File Versionname or Versioncode Without Installing APK