How to use @Preview with compose function that takes a param?

147 Views Asked by At

I have such an implementation:

...

    @Composable
    private fun PhoneVerificationCodeScreen(vm: MyViewModel) {
        Column(...) {
...
            OTPBlock(numberOfCells = NUMBER_OF_OTP_CELLS, isVerifyBtnEnabledState = vm.isVerifyBtnEnabledState)
...
        }
    }

...

There is my preview function:

    class MyViewModelProvider : PreviewParameterProvider<MyViewModel> {
        override val values: Sequence<MyViewModel> = sequenceOf(MyViewModel(
            SavedStateHandle()
        ))
    }

    @Preview(
        name = "Phone-portrait",
        device = Devices.PHONE,
        showBackground = true,
        backgroundColor = 0x111,
        showSystemUi = true
    )
    @Composable
    private fun PhonePreviewVerificationCodeScreen(
        @PreviewParameter(MyViewModelProvider::class) vm: MyViewModel
    ) = PhoneVerificationCodeScreen(vm = vm)

SPOILER: before I included ViewModel as a param it worked as expected.

I went through a few solutions in the google and the last one I tried was by using PreviewParameterProvider, however, it doesn't work as well.

So, the question is - how to "preview" the compose function that takes a param?

ERROR is:

java.lang.ClassNotFoundException: my_package.VerificationCodeViewModelProvider   at java.lang.ClassLoader.loadClass  at java.lang.ClassLoader.loadClass  at java.lang.Class.forName0  at java.lang.Class.forName  at androidx.compose.ui.tooling.ComposableInvoker.invokeComposable 

2

There are 2 best solutions below

3
Philio On BEST ANSWER

The method recommended on the Android developers site for creating previews of composables with ViewModels is to create an inner composable that accepts the ViewModel parameters as arguments:

@Composable
fun MyComposable(viewModel: MyViewModel) {

    val state by viewModel.state.collectAsStateWithLifecycle()

    MyComposable(
        state = state
    ) 
}

@Composable
fun MyComposable(state: MyState) {
   ...
}

Then create a preview for the inner composable:

@Preview
@Composable
fun MyComposablePreview(state: MyState = MyState()) {
    MyComposable(state = state)
}

See: https://developer.android.com/jetpack/compose/tooling/previews#preview-viewmodel

There are various other methods as well, such as using an interface and creating a preview instance of the interface.

1
Leonardo Velozo On

Probably not the answer you are looking for but I'd extract the viewmodel from the composables, so instead of this:

@Composable
MyComposable(viewModel: MyViewModel) {
    Button(onClick = { 
        viewModel.buttonClicked()
    }) {
        Text(text = viewModel.state.myText)
    }
}

It'd be something like this

val viewModel: YourViewModel = viewModel()
val state: MyState = viewModel.state

@Composable
MyComposable(
    state: MyState,
    onViewEvent: (MyViewEvents) -> Unit,
) {
    Button(onClick = { 
        onViewEvent(MyViewEvents.buttonClicked)
    }) {
        Text(text = state.myText)
    }
}

This also allows you to make any number of previews with different states.