$30 off During Our Annual Pro Sale. View Details »

Compose beyond Material

Compose beyond Material

Jetpack Compose is here to stay, and most teams will adopt it sooner or later. Creating Material UI with Compose is straightforward thanks to the generous amount of ready to use Material composables that the team at Google provided, what if your company has their own design system?

This session will cover the Compose blocks, explaining what you can reuse and what you’ll need to create from scratch, and show you an example of how to get started creating your own composables for your own design language, without having to reinvent the wheel.

The talk minisite is available at https://sebastiano.dev/talks/2022-compose-beyond-material.htm with links to resources, recordings and code. If you are curious of how the slides are made hit me up on Twitter and I'll happily share the source Keynote file with you!

Sebastiano Poggi

June 03, 2022
Tweet

More Decks by Sebastiano Poggi

Other Decks in Programming

Transcript

  1. Compose beyond Material Sebastiano Poggi @seebrock3r

  2. On the menu today • Do you need to ditch

    Material? • The Compose building blocks • Theming in Compose • Writing a good Compose API • Custom composables • Tips Material YES NO
  3. Hold on! Write down this link 👇 https: / /

    go.sebastiano.dev/beyond-material-2022 Look out for the symbol
  4. Hold on! Write down this link 👇 https: / /

    go.sebastiano.dev/beyond-material-2022 Look out for the symbol
  5. Material Design

  6. What is Material to you? • The default design system

    in Jetpack Compose • You use Material composables • For many, Compose == Material • Two Material versions • V2 is Material Theming • V3 is Material You • V3 is still incomplete and alpha Material Theming aka Material 2 Material You Material 3
  7. Material is very cool! • Well documented • material.io and

    m3.material.io
  8. Material is very cool! • Well documented • material.io and

    m3.material.io • Very flexible • Custom colours • Custom shapes • Custom typography Flexibility! Flexibility! Flexibility! Flexibility! Flexibility! Flexibility! Flexibility!
  9. Material is very cool! • Well documented • material.io and

    m3.material.io • Very flexible • Custom colours • Custom shapes • Custom typography Flexibility!
  10. Material is very cool! • Well documented • material.io and

    m3.material.io • Very flexible • Custom colours • Custom shapes • Custom typography Flexibility! Flexibility! Flexibility!
  11. Material is very cool! • Enough for most use-cases •

    Visual tweaks are ok • …but not for all • Behaviour changes • Incompatible semantics Flexibility! Flexibility!
  12. Suggested talks Something old, something new: Adding Jetpack Compose 


    to a large open source Android app Maia Grotepass Custom design systems in Compose Ryan Har t er Yesterday, 13 : 20 Today, 15 : 20
  13. Other design systems

  14. A design system is born • Designers hand over a

    spec • Components • Typography • Colours • Where do we star t ? • Material: yes or no? yo, new specs! do this The Designer MY SPECS 🤌
  15. A design system is born • Designers hand over a

    spec yo, new specs! do this The Designer MY SPECS 🤌 👍
  16. A design system is born • Designers hand over a

    spec • Components • Typography • Colours • Where do we star t ? • Material: yes or no? yo, new specs! do this The Designer MY SPECS 🤌 👍
  17. To Material, or not to Material • Is it compatible

    with Material? • Existing components • Amount of hacks required • Visual and behaviour • Prefer reusing Material! • Less time and effor t • Battle-tested
  18. Step 1: no more Material • You don’t want Material

    • Good news! • Views: monolithic • Compose: modular • You can get rid of Material Views
  19. Step 1: no more Material • You don’t want Material

    • Good news! • Views: monolithic • Compose: modular • You can get rid of Material Views Compose
  20. Step 1: no more Material • You don’t want Material

    • Good news! • Views: monolithic • Compose: modular • You can get rid of Material Views Compose
  21. Step 1: no more Material build.gradle.kts dependencies { // ..

    . val composeUiVersion: String by rootProject.extra implementation(“androidx.compose.ui:ui:$composeUiVersion") implementation("androidx.compose.material:material:$composeUiVersion") implementation("androidx.compose.foundation:foundation:$composeUiVersion") implementation("androidx.compose.ui:ui-tooling-preview:$composeUiVersion") debugImplementation("androidx.compose.ui:ui-tooling:$composeUiVersion") }
  22. Step 1: no more Material build.gradle.kts dependencies { // ..

    . val composeUiVersion: String by rootProject.extra implementation(“androidx.compose.ui:ui:$composeUiVersion") implementation("androidx.compose.foundation:foundation:$composeUiVersion") implementation("androidx.compose.ui:ui-tooling-preview:$composeUiVersion") debugImplementation("androidx.compose.ui:ui-tooling:$composeUiVersion") }
  23. Pro-tip time! • Sometimes you can’t take it out •

    3rd par t y dependencies • Swipe it under the rug • No auto-impor t , no code completion • More info on Intell iJ webhelp
  24. Pro-tip time! • Sometimes you can’t take it out •

    3rd par t y dependencies • Swipe it under the rug • No auto-impor t , no code completion • More info on Intell iJ webhelp
  25. Pro-tip time! • Sometimes you can’t take it out •

    3rd par t y dependencies • Swipe it under the rug • No auto-impor t , no code completion • More info on Intell iJ webhelp
  26. Done! What now? • Four main components • We took

    Material out • Most Composables are in Material • Oops. runtime material foundation ui 🫣
  27. Text Button Card Checkbox AlertDialog Divider RadioButton CircularProgressIndicator LinearProgressIndicator Snackbar

    Switch Tab TextField Snackbar Slider BottomDrawer TopAppBar FloatingActionButton Chip Surface Icon MaterialTheme NavigationRail
  28. Not just composables • Ripples • Some useful APIs •

    LocalContentAlpha • LocalContentColor • LocalTextStyle So lonely :(
  29. Not just composables • Ripples • Some useful APIs •

    LocalContentAlpha • LocalContentColor • LocalTextStyle So lonely :( LocalContentAlpha 1.0f 0.5f LocalContentColor #001E31 #006497 LocalTextStyle Regular ExtraBold So lonely :( So lonely :(
  30. WE WILL REBUILD UPON FOUNDATION

  31. WE WILL REBUILD UPON FOUNDATION

  32. @Composable fun Text( ... )

  33. @Composable fun Text( ... ) MATERIAL

  34. @Composable fun Text( ... ) MATERIAL @Composable fun BasicText( ...

    ) FOUNDATION
  35. @Composable fun Text( ... ) MATERIAL @Composable fun BasicText( ...

    ) FOUNDATION @Composable fun Layout(…) UI
  36. @Composable fun Text( ... ) MATERIAL @Composable fun BasicText( ...

    ) FOUNDATION @Composable fun Layout(…) UI
  37. @Composable fun Text( ... ) MATERIAL @Composable fun BasicText( ...

    ) FOUNDATION @Composable fun Layout(…) UI @Composable fun ComposeNode(…) RUNTIME * not 100% accurate
  38. @Composable fun MyText( ... ) MY THEME @Composable fun BasicText(

    ... ) FOUNDATION @Composable fun Layout(…) UI @Composable fun ComposeNode(…) RUNTIME * not 100% accurate
  39. Compose Foundation • Never star t ing from scratch •

    Basic building blocks • Layouts • Lazy layouts • “Bare” composables • Foundational concepts LAYOUT Box Row Column Spacer size fillMax* padding LAZY LazyRow LazyColumn LazyLayout Lazy*Grid BASE FOUNDATION Image Canvas [scrolling] [interaction] [visuals]
  40. Theming themes composingly

  41. Material as a reference • Let’s study Material • Gold

    reference implementation • Flexibility == complexity • Take what you need • Base concepts apply to other themes • Need to re-implement • But it’s easy!
  42. Composition locals • Provide values • Implicit dependency • Useful

    for common “data”
  43. Composition locals fun MyTheme() { CompositionLocalProvider(LocalContentColor provides Color.Black) { BasicText(

    text = "Hello!", style = TextStyle.Default.copy(color = LocalContentColor.current) ) } } Color.Black
  44. Composition locals @Composable fun MyTheme() { CompositionLocalProvider(LocalContentColor provides Color.Black) {

    BasicText( text = "Hello!", style = TextStyle.Default.copy(color = LocalContentColor.current) ) } } Color.Black
  45. Composition locals • Provide values • Implicit dependency • Useful

    for common “data” • Theming is built on top of it • Scoped to par t of a composition • It’s dangerous — don’t overuse! ⚠ @Composable
  46. Indications • Indicates a composable state • React to interactionSource

    • In Material it’s ripples • Draw behind or in front • Provided via composition local • Star t from DefaultDebugIndication click! IDLE HOVER PRESSED FOCUSED
  47. API design principles • Off i cial guidelines • Based

    on Kotlin conventions • Strongly recommended read • From years of experience • Helps with consistency • Meet users’ expectations
  48. API design principles • Contents are composables • Higher order

    functions • Functions with function parameters • Model variants separately • E.g., Button vs TextButton • Blog post on design choices @Composable fun MyButton( content: @Composable () -> Unit ) { // .. . } @Composable fun MyOutlineButton( content: @Composable () -> Unit ) { // .. . }
  49. Slot APIs • Design pattern from Material • Blog post

    by Chris Banes • Composables do one thing • Have “slots” for sub-components • Provide styling & pre-made components • More flexible and easier to use • Example: Scaffold
  50. Slot APIs • Design pattern from Material • Blog post

    by Chris Banes • Composables do one thing • Have “slots” for sub-components • Provide styling & pre-made components • More flexible and easier to use • Example: Scaffold topBar bottomBar floatingActionButton BottomNavigation TopAppBar FloatingActionButton
  51. Scoping with DSLs • Provide scoping if needed • Lambda

    receivers • Functions, modif i ers, etc • Examples: • Box* → BoxScope • Lazy* → LazyListScope interface LazyListScope { fun item( . .. ) { / / . .. } fun items( ... ) { / / . .. } fun stickyHeader( .. . ) { / / . .. } } interface BoxScope { @Stable fun Modifier.align( ... ): Modifier @Stable fun Modifier.matchParentSize(): Modifier }
  52. Great ar t ists steal • Use Material as reference

    • Same basics • Copy-paste is ok • Simplify things where possible • Copy patterns as well • E.g., use Color.Unspecified androidx.compose.material.Text dev.sebastiano.mytheme.Text Text
  53. Great ar t ists steal • Use Material as reference

    • Same basics • Copy-paste is ok • Simplify things where possible • Copy patterns as well • E.g., use Color.Unspecified androidx.compose.material.Text dev.sebastiano.mytheme.Text Text
  54. SO CUSTOM! YAGNI • Won’t need all of Material •

    Depends on design system • Example: indications • Buttons depress when clicked • No need for indication for that • Consider focus/hover
  55. YAGNI • Won’t need all of Material • Depends on

    design system • Example: indications • Buttons depress when clicked • No need for indication for that • Consider focus/hover SO CUSTOM!
  56. Make your Composables

  57. Let’s pretend I work at Duolingo! I don’t! I love

    you Duo please don’t sue kthxbye
  58. A button is… what? • Look at the spec •

    What is a button, really? • Something clickable • What does Material do? • Surface with onClick • Surface is a fancy Box • Add clickable(), done! Normal Pressed Disabled SO CUSTOM! SO CUSTOM! SO CUSTOM! @Composable fun Button( . .. ) { // .. . Surface( onClick = onClick, . .. ) { / / . .. } }
  59. @Composable fun Button( . .. ) { //... Box( modifier

    = modifier.clickable( . .. ), // omissis propagateMinConstraints = true ) { CompositionLocalProvider( . .. ) { ProvideTextStyle( ... ) { Row( modifier = Modifier.drawBehind( . .. ), // omissis horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, content = content ) } } } }
  60. @Composable fun Button( . .. ) { //... Box( modifier

    = modifier.clickable( . .. ), // omissis propagateMinConstraints = true ) { CompositionLocalProvider( . .. ) { ProvideTextStyle( ... ) { Row( modifier = Modifier.drawBehind( . .. ), // omissis horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, content = content ) } } } } aka poor man’s Sur f ace handle clicks set content colour set text style button background button content
  61. Text is tough, right? • Text is a royal pain

    to deal with • Layout, shaping, rendering • Emojis and fonts • What if Compose made it easy? • Enter BasicText • It’s Text, minus theming! @Composable fun Text( ... ) { val textColor = // ... val mergedStyle = / / ... BasicText(text, ... ) }
  62. @Composable fun Text( ... ) { val textColor = color.takeOrElse

    { style.color.takeOrElse { LocalPalette.current.foreground.copy(alpha = LocalContentAlpha.current) } } val combinedStyle = style.merge( TextStyle( ... ) ) BasicText( text = text, modifier = modifier, style = combinedStyle, onTextLayout = {}, overflow = overflow, softWrap = softWrap, maxLines = maxLines, inlineContent = emptyMap() ) }
  63. Text can’t be that easy! • Editable text is harder

    • Selection, cursor, clipboard, etc. • Much harder to do in Compose • …or is it? • Enter BasicTextField • It’s TextField, minus theming! • BYO “decoration” and box @Composable fun TextField( ... ) { // .. . val mergedStyle = / / ... BasicTextField( text = text, . .. , decorationBox = { .. . } ) }
  64. Text can’t be that easy! • Editable text is harder

    • Selection, cursor, clipboard, etc. • Much harder to do in Compose • …or is it? • Enter BasicTextField • It’s TextField, minus theming! • BYO “decoration” and box @Composable fun TextField( ... ) { // .. . val mergedStyle = / / ... BasicTextField( text = text, . .. , decorationBox = { .. . } ) } I’m typing here!
  65. Text can’t be that easy! • Editable text is harder

    • Selection, cursor, clipboard, etc. • Much harder to do in Compose • …or is it? • Enter BasicTextField • It’s TextField, minus theming! • BYO “decoration” and box @Composable fun TextField( ... ) { // .. . val mergedStyle = / / ... BasicTextField( text = text, . .. , decorationBox = { .. . } ) } I’m typing here!
  66. Finally, some progress (bar) • Foundation won’t help • No

    built-in composable • How does Material do it? • Time to go low-level • Drawing to the canvas • It’s pretty easy • Don’t forget LTR vs RTL TRACK HIGHLIGHT BAR
  67. Finally, some progress (bar) • Foundation won’t help • No

    built-in composable • How does Material do it? • Time to go low-level • Drawing to the canvas • It’s pretty easy • Don’t forget LTR vs RTL @Composable fun LinearProgressIndicator( ... ) { Canvas( modifier .progressSemantics( . .. ) .size( ... ) ) { drawLinearIndicatorBackground( . .. ) drawLinearIndicator( ... ) } }
  68. @Composable fun ProgressBar( .. . ) { //... Canvas( modifier

    = modifier .progressSemantics(progress) .clip(RoundedCornerShape( .. . )) .defaultMinSize(minHeight = .. . ), ) { / /.. . drawRect(color = colors.track) updateProgressBounds( ... ) drawRoundRect( . .. ) updateHighlightBounds( .. . ) drawRoundRect( . .. ) } }
  69. @Composable fun ProgressBar( .. . ) { //... Canvas( modifier

    = modifier .progressSemantics(progress) .clip(RoundedCornerShape( .. . )) .defaultMinSize(minHeight = .. . ), ) { / /.. . drawRect(color = colors.track) updateProgressBounds( ... ) drawRoundRect( . .. ) updateHighlightBounds( .. . ) drawRoundRect( . .. ) } } draw by hand “I am a progress bar” We cheat...
  70. None
  71. None
  72. None
  73. None
  74. None
  75. None
  76. Sucks!

  77. None
  78. clip()

  79. clip()

  80. clip()

  81. @Composable fun ProgressBar( .. . ) { //... Canvas( modifier

    = modifier .progressSemantics(progress) .clip(RoundedCornerShape( .. . )) .defaultMinSize(minHeight = .. . ), ) { / /.. . drawRect(color = colors.track) updateProgressBounds( ... ) drawRoundRect( . .. ) updateHighlightBounds( .. . ) drawRoundRect( . .. ) } } draw by hand “I am a progress bar” We cheat... draw track secret sauce ✨ draw progress moar secret sauce draw highlight
  82. We’re almost there…

  83. Cutting corners • Don’t overcomplicate things • YAGNI → drop

    it • Only as flexible as needed • In our example: • Don’t need shapes • “Hardcoded” params • Remember LayoutDirection @Composable fun Button( ) { ... }
  84. Cutting corners • Don’t overcomplicate things • YAGNI → drop

    it @Composable fun Button( ) { ... }
  85. Cutting corners • Don’t overcomplicate things • YAGNI → drop

    it • Only as flexible as needed • In our example: • Don’t need shapes • “Hardcoded” params • Remember LayoutDirection @Composable fun Button( ) { ... }
  86. Accessibility • Never forget a11y! • Not a corner to

    cut • It’s all about semantics • Apply semantics() modif i er • Specify roles • Test with screen readers • Docs and I/O talks
  87. What we learnt today

  88. RECAP 1. Study Material

  89. 1. Study Material 2. Use Foundation RECAP

  90. 1. Study Material 2. Use Foundation 3. Be eff i

    cient RECAP
  91. RECAP 1. Study Material 2. Use Foundation 3. Be eff

    i
  92. RECAP 1. Study Material 2. Use Foundation 3. Be eff

    i
  93. Questions?

  94. Compose beyond Material @seebrock3r https://go.sebastiano.dev/beyond-material-2022 Sebastiano Poggi Thanks Jossi 🙌

  95. Compose beyond Material @seebrock3r https://go.sebastiano.dev/beyond-material-2022 Sebastiano Poggi Thanks Jossi 🙌