Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Practical Tips and Tricks to Improve Your Compo...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →
István Juhos
September 16, 2024

Practical Tips and Tricks to Improve Your Compose Previews (KKON 2024)

Jetpack Compose Preview is a powerful tool that provides real-time feedback on how our composables are rendered and even on how they behave and animate in certain conditions. While writing useful previews seems like a straightforward task, there are some things that we need to keep in mind. Preview code won't reach users of your app (hopefully!); however, it's more likely that the author of the preview and future peers as well might have to work with them eventually.

This talk will touch on the following topics:
- Recap on the main features and limitations of Compose previews
- How we can overcome the limitations of @Preview being an annotation
- How we can make our previews developer-friendly
- What we can use our previews for besides checking how our composables are rendered and how they behave

István Juhos

September 16, 2024

Transcript

129 slides
  1. @Preview(device = "spec:width=960dp,height=1080dp,dpi=480") @Composable private fun TitleSlidePreview() { PreviewBox {

    TitleSlide() } } @Composable private fun TitleSlide() { Column( modifier = Modifier .fillMaxSize() .paint( painter = painterResource( id = R.drawable.glenn_carstens_peters, ), contentScale = ContentScale.FillBounds, ) .padding(32.dp) ) { Title( modifier = Modifier .fillMaxWidth() .padding(top = 32.dp), fontSize = 70.sp, ) Column( horizontalAlignment = CenterHorizontally, modifier = Modifier .fillMaxWidth() ) { Name( modifier = Modifier .weight(1f) .padding(bottom = 66.dp), imageHeight = 66.dp, textSize = 70.sp, ) Contacts( modifier = Modifier .padding( bottom = 16.dp, end = 16.dp, ), fontSize = 50.sp, ) } } } Practical Tips and Tricks to Improve Your Compose Previews István Juhos @stewemetal istvanjuhos.dev
  2. #KKON @stewemetal @Preview @Composable private fun ListItemPreview() { AppTheme {

    ListItem( text = "List item", onClick = {}, ) } } Compose preview
  3. #KKON @stewemetal @Preview @Composable private fun ListItemPreview() { AppTheme {

    ListItem( text = "List item", onClick = {}, ) } } Compose preview
  4. #KKON @stewemetal @Preview @Composable private fun ListItemPreview() { AppTheme {

    ListItem( text = "List item", onClick = {}, ) } } • Code Compose preview
  5. #KKON @stewemetal • Code, that • will never reach production

    @Preview @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Compose preview
  6. #KKON @stewemetal • Code, that • will never reach productio

    n​ … 🤞 @Preview @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Compose preview
  7. #KKON @stewemetal • Code, that • will never reach production…

    • others will read 🤞 @Preview @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Compose preview
  8. #KKON @stewemetal • Code, that • will never reach production…

    • others will read • needs to be maintained 🤞 @Preview @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Compose preview
  9. #KKON @stewemetal • Code, that • will never reach production…

    • others will read • needs to be maintained • might be duplicated 🤞 @Preview @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Compose preview
  10. #KKON @stewemetal • Code, that • will never reach production…

    • others will read • needs to be maintained • might be duplicated • might be used for other purposes 🤞 @Preview @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Compose preview
  11. #KKON @stewemetal Consider IDE themes @Composable fun ListItem( text: String,

    onClick: () -> Unit, modifier: Modifier = Modifier, ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier .clickable(onClick = onClick) .padding(16.dp), ) { Text( text = text, style = PreviewTheme.typography.bodyMedium, color = PreviewTheme.colors.text, modifier = Modifier.weight(1f), ) Image( imageVector = KeyboardArrowRight, contentDescription = null, colorFilter = ColorFilter.tint(PreviewTheme.colors.text) ) } }
  12. Consider IDE themes • Render Composable functions annotated with @Preview

    @Composable fun ListItem( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier .clickable(onClick = onClick) .padding(16.dp), ) { Text( text = text, style = PreviewTheme.typography.bodyMedium, color = PreviewTheme.colors.text, modifier = Modifier.weight(1f), ) Image( imageVector = KeyboardArrowRight, contentDescription = null, colorFilter = ColorFilter.tint(PreviewTheme.colors.text) ) } }
  13. Consider IDE themes • Render Composable functions annotated with @Preview

    @Composable fun ListItem( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier .clickable(onClick = onClick) .padding(16.dp), ) { Text( text = text, style = PreviewTheme.typography.bodyMedium, color = PreviewTheme.colors.text,
  14. Consider IDE themes • Render Composable functions annotated with @Preview

    @Composable fun ListItem( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier .clickable(onClick = onClick) .padding(16.dp), ) { Text( text = text, style = PreviewTheme.typography.bodyMedium, color = PreviewTheme.colors.text,
  15. #KKON @stewemetal Consider IDE themes @Preview @Composable private fun ListItemPreview()

    { AppTheme { ListItem( text = "List item", onClick = {}, ) } }
  16. #KKON @stewemetal Consider IDE themes @Preview @Composable private fun ListItemPreview()

    { AppTheme { ListItem( text = "List item", onClick = {}, ) } }
  17. #KKON @stewemetal Consider IDE themes @Preview @Composable private fun ListItemPreview()

    { AppTheme { ListItem( text = "List item", onClick = {}, ) } }
  18. #KKON @stewemetal @Preview @Composable private fun ListItemPreview() { AppTheme {

    ListItem( text = "List item", onClick = {}, ) } } Consider IDE themes
  19. #KKON @stewemetal @Preview @Composable private fun ListItemPreview() { AppTheme {

    ListItem( text = "List item", onClick = {}, ) } } Consider IDE themes
  20. #KKON @stewemetal @Preview @Composable private fun ListItemPreview() { AppTheme {

    ListItem( text = "List item", onClick = {}, ) } } Consider IDE themes
  21. #KKON @stewemetal @Preview( showBackground = true, ) @Composable private fun

    ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Consider IDE themes
  22. #KKON @stewemetal @Preview( showBackground = true, ) @Composable private fun

    ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Consider IDE themes vs
  23. #KKON @stewemetal @Preview( showBackground = true, ) @Composable private fun

    ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Use annotation parameters
  24. #KKON @stewemetal @Preview( showBackground = true, ) @Composable private fun

    ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Use annotation parameters
  25. #KKON @stewemetal @Preview( showBackground = true, backgroundColor = 0xFF_E7ECF4, )

    @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Use annotation parameters
  26. #KKON @stewemetal @Preview( showBackground = true, backgroundColor = 0xFF_E7ECF4, //

    gray.w500 ) @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } Use annotation parameters
  27. #KKON @stewemetal Use annotation parameters @Preview( showBackground = true, backgroundColor

    = GRAY_W500, ) @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } } const val GRAY_W500 = 0xFF_E7ECF4 ...
  28. #KKON @stewemetal Use annotation parameters @Preview( showBackground = true, backgroundColor

    = GRAY_W500, ) @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } }
  29. #KKON @stewemetal Use annotation parameters @Preview( showBackground = true, backgroundColor

    = GRAY_W500, uiMode = Configuration.UI_MODE_NIGHT_YES, ) @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } }
  30. #KKON @stewemetal Use annotation parameters @Preview( showBackground = true, backgroundColor

    = GRAY_W500, ) @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, ) } }
  31. #KKON @stewemetal Use annotation parameters @Preview( showBackground = true, backgroundColor

    = GRAY_W500, ) @Preview( showBackground = true, backgroundColor = GRAY_W500, ) @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, )
  32. #KKON @stewemetal Use annotation parameters @Preview( showBackground = true, backgroundColor

    = GRAY_W500, ) @Preview( showBackground = true, backgroundColor = DARK_GRAY_W500, ) @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {}, )
  33. #KKON @stewemetal Use annotation parameters @Preview( showBackground = true, backgroundColor

    = GRAY_W500, ) @Preview( showBackground = true, backgroundColor = DARK_GRAY_W500, uiMode = Configuration.UI_MODE_NIGHT_YES, ) @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {},
  34. #KKON @stewemetal Use annotation parameters @Preview( showBackground = true, backgroundColor

    = GRAY_W500, ) @Preview( showBackground = true, backgroundColor = DARK_GRAY_W500, uiMode = Configuration.UI_MODE_NIGHT_YES, ) @Composable private fun ListItemPreview() { AppTheme { ListItem( text = "List item", onClick = {},
  35. #KKON @stewemetal @Preview( name = "Light - Primary", group =

    "Light/Dark - Primary", showBackground = true, backgroundColor = GRAY_W500, ) @Preview( name = "Dark - Primary", group = "Light/Dark - Primary”, showBackground = true, backgroundColor = DARK_GRAY_W500, uiMode = UI_MODE_NIGHT_YES, ) @Composable private fun ListItemPreview() { ... } Group your previews
  36. #KKON @stewemetal @Preview( name = "Light - Secondary", group =

    "Light/Dark - Secondary", showBackground = true, backgroundColor = GRAY_W100, ) @Preview( name = "Dark - Secondary", group = "Light/Dark - Secondary", showBackground = true, backgroundColor = DARK_GRAY_W100, uiMode = UI_MODE_NIGHT_YES, ) @Composable private fun ListItemPreview() { ... } Group your previews
  37. #KKON @stewemetal @Preview( ... ) @Preview( ... ) @Preview( ...

    ) @Preview( ... ) @Composable private fun ListItemPreview() { ... } Group your previews
  38. #KKON @stewemetal @Preview( ... ) @Preview( ... ) @Preview( ...

    ) @Preview( ... ) @Composable private fun ListItemPreview() { ... } Group your previews
  39. #KKON @stewemetal @Preview( ... ) @Preview( ... ) @Preview( ...

    ) @Preview( ... ) @Composable private fun ListItemPreview() { ... } Create multipreviews
  40. @Preview( name = "Light - Primary", group = "Light/Dark -

    Primary", showBackground = true, backgroundColor = GRAY_W500, ) @Preview( name = "Dark - Primary", group = "Light/Dark - Primary", showBackground = true, backgroundColor = DARK_GRAY_W500, uiMode = UI_MODE_NIGHT_YES, ) annotation class PreviewPrimaryBackground Create multipreviews
  41. @Preview( name = "Light - Secondary", group = "Light/Dark -

    Secondary", showBackground = true, backgroundColor = GRAY_W100, ) @Preview( name = "Dark - Secondary", group = "Light/Dark - Secondary", showBackground = true, backgroundColor = DARK_GRAY_W100, uiMode = UI_MODE_NIGHT_YES, ) annotation class PreviewSecondaryBackground Create multipreviews
  42. @Preview( name = "Light - Secondary", group = "Light/Dark -

    Secondary", showBackground = true, backgroundColor = GRAY_W100, ) @Preview( name = "Dark - Secondary", group = "Light/Dark - Secondary", showBackground = true, backgroundColor = DARK_GRAY_W100, uiMode = UI_MODE_NIGHT_YES, ) annotation class PreviewSecondaryBackground Create multipreviews
  43. #KKON @stewemetal @Composable private fun ListItemPreview() { AppTheme { ListItem(

    text = "List item", onClick = {}, ) } } Create multipreviews @PreviewPrimaryBackground @PreviewSecondaryBackground annotation class PreviewAllBackgrounds
  44. #KKON @stewemetal @PreviewAllBackgrounds @Composable private fun ListItemPreview() { AppTheme {

    ListItem( text = "List item", onClick = {}, ) } } Create multipreviews @PreviewPrimaryBackground @PreviewSecondaryBackground annotation class PreviewAllBackgrounds
  45. #KKON @stewemetal Multipreview templates androidx.compose.ui.tooling.preview @PreviewLightDark @PreviewFontScale @Previe w​ ScreenSizes

    @PreviewDynamicColors ✅ developer.android.com/develop/ui/compose/tooling/previews#multipreview-templates
  46. #KKON @stewemetal No multipreview support yet @Preview @Preview annotation class

    PreviewPrimaryBackground @PreviewLightDark @PreviewFontScale @Previe w​ ScreenSizes @PreviewDynamicColors 😢 ❌
  47. #KKON @stewemetal Build custom tools - a new example @Composable

    fun PrimaryButton( text: String, @DrawableRes leadingIcon: Int? = null, onClick: () - > Unit, modifier: Modifier = Modifier ) { Button( colors = ButtonDefaults.buttonColors().copy( containerColor = AppTheme.colors.background, ), elevation = ButtonDefaults.buttonElevation(4.dp), modifier = modifier, onClick = onClick, ) { Row(verticalAlignment = CenterVertically) { leadingIcon ?. let { Image( painter = painterResource(id = leadingIcon), contentDescription = null, colorFilter = ColorFilter.tint(AppTheme.colors.primaryButtonText), modifier = Modifier.size(24.dp) ) } Text( text = text, style = AppTheme.typography.labelMedium, color = AppTheme.colors.primaryButtonText, modifier = Modifier.padding(start = 8.dp) ) } } }
  48. #KKON @stewemetal Let’s build our preview @PreviewLightDark @Composable private fun

    PrimaryButtonPreview() { PrimaryButton( text = "Primary Button", leadingIcon = R.drawable.ic_add, onClick = {}, ) }
  49. #KKON @stewemetal Let’s build our preview @PreviewLightDark @Composable private fun

    PrimaryButtonPreview() { PrimaryButton( text = "Primary Button", leadingIcon = R.drawable.ic_add, onClick = {}, ) }
  50. #KKON @stewemetal Something’s missing… @PreviewLightDark @Composable private fun PrimaryButtonPreview() {

    PrimaryButton( text = "Primary Button", leadingIcon = R.drawable.ic_add, onClick = {}, ) }
  51. #KKON @stewemetal @PreviewLightDark @Composable private fun PrimaryButtonPreview() { AppTheme {

    PrimaryButton( text = "Primary Button", leadingIcon = R.drawable.ic_add, onClick = {} ) } } Something’s missing… the theme 🤦
  52. #KKON @stewemetal @PreviewLightDark @Composable private fun PrimaryButtonPreview() { AppTheme {

    PrimaryButton( text = "Primary Button", leadingIcon = R.drawable.ic_add, onClick = {} ) } } … and flexibility 💧
  53. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, content:

    @Composable () -> Unit, ) { AppTheme { content() } } Building preview tools
  54. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, content:

    @Composable () -> Unit, ) { AppTheme { Box { content() } } } Building preview tools
  55. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, backgroundColor:

    Color = AppTheme.colors.backgroundSecondary, content: @Composable () -> Unit, ) { AppTheme { Box { content() } } } Building preview tools
  56. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, backgroundColor:

    Color = AppTheme.colors.backgroundSecondary, content: @Composable () -> Unit, ) { AppTheme { Box( modifier = modifier .background(backgroundColor), ) { content() } } } Building preview tools
  57. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, backgroundColor:

    Color = AppTheme.colors.backgroundSecondary, content: @Composable () -> Unit, ) { AppTheme { Box( modifier = modifier .background(backgroundColor), ) { content() } } } Building preview tools
  58. #KKON @stewemetal Building preview tools - theme insights AppTheme.colors.backgroundSecondary object

    AppTheme { val colors: AppColors @Composable @ReadOnlyComposable get() = LocalAppColors.current //... }
  59. #KKON @stewemetal Building preview tools - theme insights AppTheme.colors.backgroundSecondary object

    AppTheme { val colors: AppColors @Composable @ReadOnlyComposable get() = LocalAppColors.current //... } val LocalAppColors = staticCompositionLocalOf<AppColors> { error("Make sure to use this value in an AppTheme.") }
  60. #KKON @stewemetal Building preview tools - theme insights AppTheme.colors.backgroundSecondary object

    AppTheme { val colors: AppColors @Composable @ReadOnlyComposable get() = LocalAppColors.current //... } val LocalAppColors = staticCompositionLocalOf<AppColors> { lightColors() }
  61. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, backgroundColor:

    Color = AppTheme.colors.backgroundSecondary, content: @Composable () -> Unit, ) { AppTheme { Box( modifier = modifier .background(backgroundColor), ) { content() } } } Building preview tools
  62. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, backgroundColor:

    Color = AppTheme.colors.backgroundSecondary, content: @Composable () -> Unit, ) { AppTheme { Box( modifier = modifier .background(backgroundColor), ) { content() } } } Building preview tools
  63. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, backgroundColor:

    @Composable () -> Color = { AppTheme.colors.backgroundSecondary }, content: @Composable () -> Unit, ) { AppTheme { Box( modifier = modifier .background(backgroundColor), ) { content() } } } Building preview tools
  64. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, backgroundColor:

    @Composable () -> Color = { AppTheme.colors.backgroundSecondary }, content: @Composable () -> Unit, ) { AppTheme { Box( modifier = modifier .background(backgroundColor()), ) { content() } } } Building preview tools
  65. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, backgroundColor:

    @Composable () -> Color = { AppTheme.colors.backgroundSecondary }, content: @Composable () -> Unit, ) { AppTheme { Box( modifier = modifier .background(backgroundColor()), ) { content() } } } Building preview tools
  66. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, backgroundColor:

    @Composable () -> Color = { AppTheme.colors.backgroundSecondary }, content: @Composable () -> Unit, ) { AppTheme { Box( ... ) { content() } } } Building preview tools
  67. #KKON @stewemetal @Composable fun PreviewBox( modifier: Modifier = Modifier, backgroundColor:

    @Composable () -> Color = { AppTheme.colors.backgroundSecondary }, paddingValues: PaddingValues = PaddingValues(4.dp), content: @Composable () -> Unit, ) { AppTheme { Box( ... ) { content() } } } Building preview tools
  68. #KKON @stewemetal @Composable fun Previe w​ Box( ... content: @Composable

    () -> Unit, ) { AppTheme { Box( ... ) { content() } } } Building preview tools
  69. #KKON @stewemetal @Composable fun Previe w​ Column( ... content: @Composable

    () -> Unit, ) { AppTheme { Column( ... ) { content() } } } Building preview tools
  70. #KKON @stewemetal Override CompositionLocals @Composable fun PreviewColumn( ... layoutDirection: LayoutDirection

    = LayoutDirection.Ltr, content: @Composable () -> Unit, ) { AppTheme { Column( ... ) { content() } } }
  71. #KKON @stewemetal Override CompositionLocals @Composable fun PreviewColumn( ... layoutDirection: LayoutDirection

    = LayoutDirection.Ltr, content: @Composable () -> Unit, ) { CompositionLocalProvider( LocalLayoutDirection provides layoutDirection, ) { AppTheme { Column( ... ) { content() } } } }
  72. #KKON @stewemetal Override CompositionLocals @PreviewLightDark @Composable private fun PreviewColumnPreview() {

    PreviewColumn( layoutDirection = LayoutDirection.Rtl, ) { PrimaryButton( ... ) ListItem( ... ) } }
  73. #KKON @stewemetal Override CompositionLocals @PreviewLightDark @Composable private fun PreviewColumnPreview() {

    PreviewColumn( layoutDirection = LayoutDirection.Rtl, ) { PrimaryButton( ... ) ListItem( ... ) } }
  74. #KKON @stewemetal Override CompositionLocals @PreviewLightDark @Composable private fun PreviewColumnPreview() {

    PreviewColumn( layoutDirection = LayoutDirection.Rtl, ) { PrimaryButton( .. . ) ListItem( .. . ) } }
  75. #KKON @stewemetal Override CompositionLocals @Preview @Composable private fun PreviewColumnPreview() {

    PreviewColumn( layoutDirection = LayoutDirection.Rtl, ) { PrimaryButton( .. . ) ListItem( .. . ) } }
  76. #KKON @stewemetal Override CompositionLocals @Preview @Composable private fun PreviewColumnPreview() {

    PreviewColumn( layoutDirection = LayoutDirection.Rtl, ) { PrimaryButton( .. . ) ListItem( .. . ) } }
  77. #KKON @stewemetal Some more ideas • Generate previews with PreviewParemeterProviders

    • Providing Composables as preview parameters 🤯 proandroiddev.com/using-previewparameters-and-providing-composables-to-jetpack-compose-previews-5b1f5a8fe192
  78. #KKON @stewemetal Some more ideas • Use previews to build

    a component showcase github.com/airbnb/Showkase
  79. #KKON @stewemetal Some more ideas • Use previews for screenshot

    testing developer.android.com/studio/preview/compose-screenshot-testing
  80. #KKON @stewemetal Some more ideas • … or just make

    the whole app runnable in Preview 🤷
  81. #KKON @stewemetal Some more ideas • … or just make

    the whole app runnable in Preview 🤷 medium.com/whatnot-engineering/preview-driven-development-with-compose-f7a5beee95aa
  82. #KKON @stewemetal Resources • developer.android.com/develop/ui/compose/tooling/previews • developer.android.com/topic/performance/rendering/overdraw • proandroiddev.com/using-previewparameters-and-providing-composables- to-jetpack-compose-previews-5b1f5a8fe192

    • github.com/airbnb/Showkase • developer.android.com/studio/preview/compose-screenshot-testing • medium.com/whatnot-engineering/preview-driven-development-with- compose-f7a5beee95aa • Good old fi eld experience 😅
  83. Practical Tips and Tricks to Improve Your Compose Previews •

    Compose previews are code • Simplify previews as much as possible (but go wild if it’s worth it!) • Compose custom reusable tools istvanjuhos.dev @stewemetal István Juhos Photo by Glenn Carstens-Peters

More Decks by István Juhos

Other Decks in Programming