DEV Community

Mitchell Giles
Mitchell Giles

Posted on

Power Up Your Jetpack Compose Previews

Jetpack Compose is the go to UI framework for modern Android app development. One of Compose's most useful features is the ability to easily preview your UI without having to run the entire app. These previews are very flexible, dynamic, and easy to write.
So lets explore some of the ways we can get more out of Compose Previews.

The Preview Pane
Any Composable function that is annotated with @Preview will generate a preview of your function. These previews will show up in the preview pane, and this pane has some fun functionality built in. Each one of these previews that show have some unique functionality available to them.

  • UI Check Mode. Starting in Android Studio Iguana you can use the UI Check Mode to easily find adaptive and accessibility issues with your UI.

  • Animation Preview. If you composable function has any animations inside of it, when you make a preview you can also view the Animation Preview. Inside this unique type of preview view you can play your animation, scrub through it, and turn on and off pieces of the animation.

  • Interactive Mode. With Interactive Mode you can easily verify that your UI behaves correctly when it is being used. You can click on components works verifying that the interactions are correct (such as the ripple showing properly), scroll, and so much more.

  • Run Preview. This makes it so the preview can be run on a connected device or emulator. Making it so you can easily see how the UI looks on a real device without building your whole app.

The Preview Annotation Class
The @Preview that we add to make previews is actually a pretty powerful little piece of code. It is an Annotation Class that has a lot of optional parameters that we can use to change some how the preview is rendered and mimic some system level settings. This includes things like: locale, light or dark mode, font scales, and a lot more.

As of Compose 1.5.4 the current parameters are:

val name: String = "",
val group: String = "",
@IntRange(from = 1) val apiLevel: Int = -1,
val widthDp: Int = -1,
val heightDp: Int = -1,
val locale: String = "",
@FloatRange(from = 0.01) val fontScale: Float = 1f,
val showSystemUi: Boolean = false,
val showBackground: Boolean = false,
val backgroundColor: Long = 0,
@UiMode val uiMode: Int = 0,
@Device val device: String = Devices.DEFAULT,
@Wallpaper val wallpaper: Int = Wallpapers.NONE,
Enter fullscreen mode Exit fullscreen mode

For the following examples will be using this composable function and class:

class Person(
    val firstName: String,
    val lastName: String,
    val phone: String,
    val email: String,
    val profilePictureUrl: String
) {
    val fullName = "$firstName $lastName"
}

@Composable
fun BasicCard(
    person: Person,
    modifier: Modifier = Modifier
) {
    Card(
        modifier = modifier.fillMaxWidth(),
    ) {
        Row(
            verticalAlignment = Alignment.CenterVertically
        ) {
            Icon(
                imageVector = ImageVector.vectorResource(id = R.drawable.ic_launcher_foreground),
                contentDescription = "Preview Profile Pic"
            )
            Column(
                modifier = Modifier.padding(end = 8.dp).padding(vertical = 8.dp)
            ) {
                Text(
                    text = person.fullName,
                    style = MaterialTheme.typography.headlineMedium,
                )
                Text(
                    text = person.phone,
                    style = MaterialTheme.typography.bodyMedium,
                )
                Text(
                    text = person.email,
                    style = MaterialTheme.typography.bodyMedium,
                )
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And this is the basic preview function for it:

@Preview
@Composable
private fun BasicCardPreview() {
    MaterialTheme {
        BasicCard(
            modifier = Modifier.padding(8.dp),
            person = Person(
                firstName = "John",
                lastName = "Doe",
                phone = "(801) 123-4567",
                email = "john.doe@preview.com",
                profilePictureUrl = "https://www.google.com"
            )
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

And here is the preview that is generated:
A screenshot of what the basic card looks like

Lets start exploring some of the things we parameters we can use.

If we replace the @Preview with this @Preview(showBackground = true, backgroundColor = 0xFFBA1B1B) our generated preview now looks like this:
A screenshot showing a basic card with a red background behind the card
So now we can more easily see the curved corners and how it looks with spacing.

While its nice to set a background when viewing individual components, the power of previews goes far beyond that. What makes compose previews really powerful is being able to adjust things like UI Mode (light vs dark mode) or Font Scales that are used with accessibility.
To test out the font scale we can utilize another cool feature of compose functions. We can actually stack multiple preview annotations and Android Studio will generate a preview for each one.

So lets replace that previous preview annotation with this:

@Preview(
    name = "Large Font",
    fontScale = 2f
)
@Preview(
    name = "Normal Font",
    fontScale = 1f
)
Enter fullscreen mode Exit fullscreen mode

Now our preview pane has these two generated:
A screenshot showing two different basic cards with varying font sizes

Great! Now we can easily see what our components are going to look like with a variety of different settings. But there is one small problem. It can be annoying to have to add all these additional preview annotations to every one of our composables that we are wanting to preview. Luckily there is an answer for that: Multipreview Annoations.

With Multipreview Annoations we can create our own annotation classes that have these multiple different previews. Starting in Compose 1.6.0, compose will come with a few of these prebuilt (@PreviewScreenSizes, @PreviewFontScales, @PreviewLightDark, and @PreviewDynamicColors).

To make our own multipreview annotation its as simple. As an example, say we are on Compose 1.5.4 so we don't have access to the prebuilt multipreview templates we can make our own by doing something like this:

@Preview(
    name = "small font",
    group = "font scales",
    fontScale = 0.5f
)
@Preview(
    name = "normal font",
    group = "font scales",
    fontScale = 1f
)
@Preview(
    name = "medium font",
    group = "font scales",
    fontScale = 1.5f
)
@Preview(
    name = "large font",
    group = "font scales",
    fontScale = 2f
)
annotation class FontScalePreviews
Enter fullscreen mode Exit fullscreen mode

Now if we replace the @Preview annotation with @FontScalePreviews we will get these generated previews:
A screenshot showing several basic cards with varying font sizes

Note: We use the group parameter so inside the preview pane we can easily switch between showing the previews for "All", "font scales", or any other groups we define.

Preview Parameter Provider
There is still one more trick we can use to make our previews do more for us. In the above BasicCard examples we were only showing the same info in every preview. But that might not be accurate of the actual functionality of the composable function. Composable functions often need to handle it being in different states. In this example, that could mean states where the user has really a really long name or maybe they have an empty phone number. We can easily just have multiple preview functions that have a different Person passed into each one. But then we are maintaining a lot more functions and have duplicate code. The solution to this are: Preview Parameter Providers.

When we are working with a composable function that takes in a state object it can be beneficial to write a Preview Parameter Provider.

To make one we make a class that implements PreviewParameterProvider and overrides the values variable. For our Person class it would end up looking something like this:

private class PersonProvider: PreviewParameterProvider<Person> {
    override val values: Sequence<Person>
        get() = sequenceOf(
            Person(
                firstName = "John",
                lastName = "Doe",
                phone = "(801) 123-4567",
                email = "john.doe@preview.com",
                profilePictureUrl = "https://www.google.com"
            ),
            Person(
                firstName = "Jane",
                lastName = "Smith",
                phone = "",
                email = "jane.smith@preview.com",
                profilePictureUrl = "https://www.google.com"
            ),
        )
}
Enter fullscreen mode Exit fullscreen mode

And to use it we can rewrite our preview function to this:

@Preview
@Composable
private fun BasicCardPreview(
    @PreviewParameter(PersonProvider::class) person: Person
) {
    MaterialTheme {
        BasicCard(
            modifier = Modifier.padding(8.dp),
            person = person
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Now our generated previews look like this:
A screenshot showing multiple Basic Cards with different information in each one

Would you look at that! We can easily see that there is an issue when the phone number is empty. So we could go in and fix that.

Wrap Up
If we utilize the available parameters inside the Preview Annotation Class we can easily make our previews reflect various system/device states and configurations. And if we use Preview Parameter Providers we can easily show our UI in various states of data. All without having to run our full app.
That coupled with Live Edit and Live Literals we can easily iterate over our UI as we build it out and make changes.

Top comments (0)