45

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

musso
  • 501
  • 1
  • 4
  • 5
  • currently the preview works if pass viewModel through func parameter, but it s weird that if I have many viewmodels – musso Nov 15 '20 at 06:53
  • 3
    `@Preview` is meant mostly for the ends of the composable hierarchy (e.g., rows in a list), and those composables should neither receive nor instantiate viewmodels. Rather, they should receive normal parameters, `State` objects, and lambdas for callbacks -- things that you can easily provide default values for in the composable declaration. – CommonsWare Nov 15 '20 at 11:57
  • 1
    I can see some using `mock` when testing. Wonder if there is any similar thing when using preview. It will be inconvenient of not able to preview a parent screen if any of children composable using any viewmodel – musso Nov 15 '20 at 12:15
  • @CommonsWare Not according to this doc. Composables can take a viewmodel. I think the Android team just needs to add the support for it: https://developer.android.com/jetpack/compose/state – Johann Feb 22 '21 at 20:03
  • 1
    I faced up the same problem. My workaround is to made my viewModel nullable and provide default value for the preview. – Mayhem50 Jun 15 '21 at 09:37
  • There is a `androidx.lifecycle.viewmodel.compose.viewModel` but, I preview didn't work either with this. – Dr.jacky Jul 21 '21 at 15:02
  • Posting your source code might help in finding the problem. – Mohamed Medhat Aug 16 '21 at 15:27
  • https://jetc.dev/slack/2021-04-17-preview-viewmodel.html – Jemshit Aug 24 '21 at 09:11
  • Check by removing other components. I had that error and it wasn't because of my viewmodel but because of another material component, which sadly I now forget. – mmm111mmm Sep 28 '21 at 13:57
  • Does this answer your question? [How to get preview in composable functions that depend on a view model?](https://stackoverflow.com/questions/69089816/how-to-get-preview-in-composable-functions-that-depend-on-a-view-model) – Chetan Gupta Dec 16 '21 at 21:32
  • Hi I have figure out a way to run preview composable on emulator even if they are hilt ViewModel injected check [here](https://stackoverflow.com/a/70385823/6938847) – Chetan Gupta Dec 16 '21 at 21:44

6 Answers6

17

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

C. Hellmann
  • 556
  • 6
  • 13
  • 14
    I'm using hilt and when I use `= hiltViewModel()` it still shows: `ViewModels creation is not supported in Preview` – Dr.jacky Oct 03 '21 at 11:35
  • The example tries to show the situation where your UI and the ViewModel are decoupled. The only way the viewmodel talks to the UI is trough method references. So if you are getting this error is because your ViewModel is in the @Preview which it shouldn't. – C. Hellmann Oct 04 '21 at 12:54
  • @Dr.jacky u find solution for yours case? – Morozov Oct 04 '21 at 15:33
  • @Morozov https://stackoverflow.com/questions/69089816/how-to-get-preview-in-composable-functions-that-depend-on-a-view-model/69089891?noredirect=1#comment122708029_69089891 – Dr.jacky Oct 04 '21 at 15:53
  • He is doing it in a way that is not supposed to be done. You cannot initialize a viewmodel inside of a preview – C. Hellmann Oct 05 '21 at 08:32
  • @Morozov Follow what C. Hellmann did; As google does the same: https://developer.android.com/jetpack/compose/state#viewmodel-state;;; Create an extra composable (`HelloContent`) and use THAT in the preview – Dr.jacky Oct 05 '21 at 11:28
  • Th explanation given as the correct answer in the provided link is insane. Creating a fake repository. Just provide the methods you need as parameters for the UI Composable and if you need any data just provide some mocks. – C. Hellmann Oct 05 '21 at 11:55
  • This doesn't work if your composable uses any other composables that uses a viewmodel. Say, if your Scaffold contains any buttons that instantiate a viewmodel, you cannot preview your scaffold – MikkelT Jan 28 '23 at 19:39
  • This does work, but you need to adapt your approach as no solution fits all problems. Also, most people that disagreed to this solution were following an anti pattern approach. I recommend you review your implementation with code from trusted sources. – C. Hellmann Jan 30 '23 at 00:28
  • Yes, it works in small, bottom tree composables, but it is infeasible for even med-sized apps. Using the `viewModel()` or `hiltViewmodel()` shouldn't be anti-pattern when it is literally a standard component in androidx. As it is right now, The only way to use @Preview is for your top-level composable to have an insane and unmanagable amount of parameters! That's just *not* feasible or even fun to work with. They should really rework either viewmodels-within-composables or the @Preview-feature. We need to be able to access our root level viewmodel anywhere in the tree AND use @Preview. – MikkelT Jan 31 '23 at 21:28
12

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 ""
    }
}
Blindsurfer
  • 363
  • 1
  • 3
  • 9
4

Since LiveEdit is now available, you don't need to use Compose Preview for a whole screen with ViewModel anymore. Just debug the app with Live Edit. This is also how it's done in Flutter with "hot reload". In Flutter there isn't even something like Compose Preview.

I would recommend using Compose Preview only for smaller composables that don't have a ViewModel.

So you don't have to write all this boilerplate code with extending the ViewModel or passing primitives from the ViewModel into a sub composable.

Keep in mind that one requirement to use LiveEdit, is to use AndroidStudio Flamingo or later.

1

The @Preview annotated composable class should be agnostic from viewmodel. i use this solution adding an extra class with models as input

@Composable
fun TabScreen(
    viewModel: DetailsViewModel = viewModel()
) {
    val details = viewModel.details.observeAsState()

    Tabs(
        details.value
    )
}

@Composable
fun Tabs(
    details: Details?
) {
    val pagerState = rememberPagerState()
    val coroutineScope = rememberCoroutineScope()
    
}


@Composable
@Preview(showBackground = true)
fun TabsPreview(
    @PreviewParameter(DetailsPreview::class)
    details: Details?
) {
    AppTheme {
        Tabs(details)
    }
}
L.Grillo
  • 960
  • 3
  • 12
  • 26
0

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

blacktiago
  • 351
  • 2
  • 11
-3

what I am using:

  @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
         MyMVIApp1Theme {
           val myViewModel = remember { AppViewModel() }
           Greeting(viewModel = myViewModel,"Android")
         }
    }
user3439901
  • 1
  • 1
  • 1
  • constructor of your viewModel doesn't have any parameters?) like injected repositories/interactors? – user924 Jul 14 '22 at 13:04