Jetpack Compose and Composable Preview

Make the most out of the preview screen during iterative development

Tomáš Repčík
Better Programming

--

Photo by Andrew M on Unsplash

With the new Android Studio Electric Eel, the support for composable previews got new features like the definition of Android device size, multiple previews with the class definition, sample data sources and more.

Composable Previews give you the option to see the actual state of your composables without an emulator. So, the developer can iterate faster with various settings and get to the final desired composable view.

Basic usage

If a new composable is created in Android studio, the second function with a tag @Preview is generated underneath, which serves as a starting point for your previews. The following screen is used as an example of how to create better previews.

If needed parameters are added to our composable, we will get the following result. If the preview is missing in your composable file, start to write prev and a hint from Android Studio appears. By clicking enter, the Android Studio will generate all the needed code.

@Preview
@Composable
fun WelcomeScreenPreview() {
AppTheme {
val navController = rememberNavController()
WelcomeScreen(navController = navController)
}
}
Default preview of composable

To make it look much more realistic, we can add a couple of parameters to the @Preview.

  • showBackground — the background of the device is filled by default colour or by colorBackground parameter
  • backgroundColor — is 32bit ARGB colour in Long. You need to multiply the hex colour representation to a Long number manually because the number needs to be compiler constant.
  • showSystemUi — shows the bottom navigation top bar with the time
  • name — shows the custom name for the preview
  • apiLevel — which target Android version should be used
  • fontScale — the scaling of the font based on the settings of the phone
  • locale — to change the used locale by the view
  • uiMode — can change UI colour masking in accordance to choice (e.g. to dark mode)
@Preview(
showBackground = true,
backgroundColor = 256 * 256 * 256, // R * G * B
showSystemUi = true,
name = "Welcome Screen",
fontScale = 1f,
local = "en"
apiLevel = Build.VERSION_CODES.TIRAMISU,
uiMode = Configuration.UI_MODE_TYPE_NORMAL
)

To avoid integrating background, put into your compose tree the Surface, Scaffoldor other composable with background and it will create the background for you.

Sizing of the Preview

@Preview implements the sizing of the screen. The screen can be previewed at multiple screen sizes at once. You can choose predefined devices or your width and height too. Writing id or spec inside of the string will show hints.

// for defining the specific device - names are checked by IDE
@Preview(device = "id:pixel_5")
// for defining the screen specifications - values are checked by IDE too
@Preview(device = "spec:width=411dp,height=891dp,dpi=410,orientation=portrait")

Reusing Preview Settings

Preview tags can be aggregated into one class and repurposed for any other view. Creating multiple previews for one composable is not needed anymore. To create multiple screen configurations, the annotation class needs to be defined:

@Preview(device = "id:pixel_5")
@Preview(device = "id:pixel") // you can add any number of previews together
annotation class MultipleScreenSizePreview
@MultipleScreenSizePreview
@Composable
fun WelcomeScreenPreview() {
AppTheme {
Surface {
val navController = rememberNavController()
WelcomeScreen(navController = navController)
}
}
}
Multiple screen preview

Fell free to mix different devices and screen size specifications.

You can mix your custom classes too!

// set of bright screens
@Preview(device = "id:pixel_5")
@Preview(device = "id:pixel")
annotation class BrightScreens

// set of dark screens
@Preview(device = "id:pixel_5", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(device = "id:pixel", uiMode = Configuration.UI_MODE_NIGHT_YES)
annotation class DarkScreens

// combination of previews
@BrightScreens
@DarkScreens
@Composable
fun WelcomeScreenPreview() {
AppTheme {
Surface {
val navController = rememberNavController()
WelcomeScreen(navController = navController)
}
}
}
Combined previews of dark and bright screens of the same composable

Adding sample data to preview

The composables need some data to show or state to show. In most cases, it is enough to pass one or two parameters manually. However, if there are many states or big data to pass, the code gets stuffed with all the inputs.

The preview enables us to create a data provider where the data can be separated, loaded and passed to the preview.

Here is an example of the screen, which can go among loading, success and error states.

Here are the available states:

sealed class MainState {
object LoadingState : MainState()
object SuccessState : MainState()
object ErrorState : MainState()
}

To create PreviewParameterProvider<T>, the provider needs to implement this interface and define the data class. Here comes the convenience of sealed classes.

class MainStateProvider : PreviewParameterProvider<MainState> {
override val values = sequenceOf(
MainState.LoadingState,
MainState.SuccessState,
MainState.ErrorState,
)
}

The Values field is overridden and gives us the place to put our custom data. For every entry, the provider will create a new Preview. The input parameter is annotated with our provider @PreviewParameter(MainStateProvider::class) and the compiler will take care of the rest.

@Preview(device = "id:pixel_5")
@Composable
fun MainScreenPrev(@PreviewParameter(MainStateProvider::class) state: MainState) {
MainScreen(state)
}
Three different states defined through PreviewParameterProvider

The usage is not limited to states. Explore other approaches to preview the screen with various input data and only one provider.

Final Thoughts

  • Previews enable you to quickly see changes, and iterate and compare views without launching the emulator.
  • Creation of previews will force you to keep separate logic and UI because it will consume less time.
  • To get instant feedback across multiple device configurations in one grid.
  • Previews can create images by right-clicking on them. The images can be shared with other developers or managers for quick feedback.
  • Unfortunately, previews are not replacements for UI testing and end-to-end testing.
  • Keep it simple!

Thanks for reading!

Resources

--

--

https://tomasrepcik.dev/ - Flutter app developer with experience in native app development and degree in biomedical engineering.