Jetpack Compose: Using Preview
The way we preview our UI layouts (i.e. composables) done using Jetpack Compose is a bit different from the XML View approach. We have to write dedicated preview functions to generate the previews we need.
By saying so, unlike the XML Views that we can only preview the exact layout we have built, the Jetpack Compose preview function is much more flexible and allow us to generate multiple previews for different purposes.
Setting up previews
Additional dependency required:
debugImplementation "androidx.compose.ui:ui-tooling:1.1.1"
implementation "androidx.compose.ui:ui-tooling-preview:1.1.1"
The basics
Let’s say we have these composable functions.
@Composable
fun SimpleComposable() {
Text("Hello World")
}
@Composable
fun TextComposable(text: String) {
Text(text = text)
}
Setting up a preview function is easy. If the composable function takes no parameters, we can just add @Preview
to the same composable function.
@Preview
@Composable
fun SimpleComposable() {
Text("Hello World")
}
@Preview
@Composable
fun TextComposablePreview() {
TextComposable(text = "Some other text")
}
@Preview
is configurable. We can preview the same composable function under different settings. Some common parameters are:
@Preview(
name = "api21",
group = "SimpleComposablePreview",
apiLevel = 21,
widthDp = 800,
heightDp = 400,
locale = "ja_JP",
fontScale = 1.0f,
showSystemUi = true,
// Transparent background by default. Override using both below
showBackground = true,
backgroundColor = 0xFFFFFFFF,
device = Devices.PIXEL_C
)
@Composable
fun TextComposablePreview() {
TextComposable(text = "Some other text")
}
- If
name
is set, on the preview pane that name is displayed instead of the preview function name. - If
group
is set, we are able to filter previews by group.
Multipreview Annotations
So if we have a predefined set of preview arguments and we want to test our different composables against the same set of configurations, we can define a custom annotation class.
@Preview(
name = "api 20",
group = "apiLevels",
apiLevel = 20
)
@Preview(
name = "api 21",
group = "apiLevels",
apiLevel = 21
)
annotation class apiLevelPreviews
Then previews will be generated automatically without repeating the same settings everywhere. (Reducing boilerplate code you know! I hate this term though)
@apiLevelPreviews
@localePreviews
@Composable
fun HelloWorldPreview() {
Text("Hello World")
}
We can define and apply multiple preview annotations, but they do not mutate. Each annotation is rendered independently. That is, if @apiLevelPreviews
has 2 previews, and @localePreviews
has 10 previews, you will get 12 previews instead of 20 generated. Here the group
plays a key role in filtering the previews you need to see on the preview pane.
Preview parameters
Composable functions with non-default parameters are not supported in Preview unless they are annotated with @PreviewParameter
.
class SampleUserProvider: PreviewParameterProvider<User> {
override val values = sequenceOf(
User("Sainsburys",30),
User("Morrisons",50)
)
}
@Preview
@Composable
fun UserInfo(@PreviewParameter(SampleUserProvider::class,1) user:User) {
Text(user.name+ " "+user.age)
}
View Model preview trick using preview parameters
If we use dependency injection to provide a view model instance to a composable function, we can see a preview rendering error as the view model is not currently supported.
One way to work around this is to use @PreviewParameter
to provide a mock view model (assume you have one, and you should have a view model interface to facilitate testing and previews).
class AboutScrenPreviewParameterProvider
: PreviewParameterProvider<AboutScreenViewModel> {
override val values = sequenceOf(
AboutScreenViewModel(
...
...
)
)
}
@Preview
@Composable
fun AboutScreenPreview(
@PreviewParameter(AboutScreenPreviewParameterProvider::class)
aboutScreenViewModel: AboutScreenViewModel
) {
AboutScreen(viewmodel = aboutScreenViewModel)
}
Why Jetpack Compose Previews are useful and important
When being designed, the whole Kotlin Compose/ AndroidX Jetpack Compose was particularly done to ensure things could be better tested.
We can now properly generate previews programmatically for the modern snapshot tests easily. Also, as shown above, we can systematically define a set of environments to immediately check how the composable functions look with different locales, font scales, screen dimensions, API levels etc. Without this, we might have to rely on complex UI tests, integration tests, or manual QA tests.
I am not saying that the previews can replace all those tests. Still, developers can spot something unusual earlier while coding than later found in other testing stages — provided the previews are carefully designed, set up, and used.
Android Studio is still not doing a great job rendering previews, but the development team is committed to improving in that area. It is important to keep our Android Studio updated to enjoy these benefits.