ViewModels creation is not supported in Preview

Viewed 7995

I followed the official guide to create viewModel instance and it works perfectly. However, when there is any viewModel in the @composable, Android Studio isn't able to render the preview and with the error code ViewModels creation is not supported in Preview. Anyone got any solution?

P.S. using compose version 1.0.0-alpha06

4 Answers

You could use an approach that looks like this which will show up in the recommended video bellow:

@Composable
fun TestView(
    action: MainActions,
    viewModel: OnboardViewModel = getViewModel()
) {
    TestUI(onClick = viewModel.clickMethod())
}

@Composable
fun TestUI(onClick: () -> Unit) {}

@Preview
@Composable
fun TestUIPreview() {
    MaterialTheme() {
        TestUI(onClick = {})
    }
}

There is a recommendation from google in this video at the selected time: https://youtu.be/0z_dwBGQQWQ?t=573

I had exactly the same problem. The solution was: Extend the ViewModel with an interface

ComposeView:

@Composable
fun MyScreen(myVm: IMyViewModel = MyViewModel()) {
    Text(text = myVm.getTextA())
}


@Preview()
@Composable
fun MyScreenPreview() {
    MyScreen(myVm = MyViewModelPreview())
}

ViewModel:

abstract class IMyViewModel : ViewModel(){
    abstract val dynamicValue: StateFlow<String>
    abstract fun getTextA() : String
}

class MyViewModel : IMyViewModel() {
    private val _dynamicValue: MutableStateFlow<String> = MutableStateFlow("")
    override val dynamicValue: StateFlow<String> = _dynamicValue

    init {
    }

    override fun getTextA(): String {
        return "Details: ${EntityDb.getAllEntities().lastOrNull()?.details}"
    }
}

class MyViewModelPreview(override val dynamicValue: StateFlow<String> =    MutableStateFlow("no data")) : IMyViewModel() {
    override fun getTextA(): String {
        return ""
    }
}

You could use interfaces and hilt.

interface IMyViewModel {

    fun getTextA() : String

}

@HiltViewModel
class MyViewModel() : ViewModel(), IMyViewModel {
    fun getTextA() : String {
        //do some cool stuff
    }
}

class MyViewModelPreview() : IMyViewModel {
    fun getTextA() : String {
        //do some mock stuff
    }
}

@Composable
fun MyScreen(myVm = hiltViewModel<MyViewModel>()) {
    Text(text = myVm.getTextA())
}


@Preview()
@Composable
fun MyScreenPreview() {
    MyScreen(myVm = MyViewModelPreview())
}

In this point MyViewModel is an implementation of IMyViewModel annotated with @HiltViewModel and hiltViewModel make all the required wired for you, In preview you could use any other simple mock implementation.

If you need to provide some dependency to your view model use injected constructor with dagger injection(already supported by hilt). Obviously this dependency should be paced on your actual viewmodel and your preview implementations need to be just a wrapper class with no other dependency since they function is just satisfy arguments

@HiltViewModel
class MyViewModel @Inject constructor(
    private val myDependencyRepositoryOne: MyDependencyRepositoryOne,
    private val myDependencyRepositoryTwo: MyDependencyRepositoryTwo)
: ViewModel(), IMyViewModel {
    fun getTextA() : String {
        //do some cool stuff
    }
}

Here is another useful resource related to viewmodel injection in compose

what I am using:

  @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
         MyMVIApp1Theme {
           val myViewModel = remember { AppViewModel() }
           Greeting(viewModel = myViewModel,"Android")
         }
    }
Related