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

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. 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
  2. Hold on! Write down this link 👇 https: / /

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

    go.sebastiano.dev/beyond-material-2022 Look out for the symbol
  4. 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
  5. 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!
  6. Material is very cool! • Well documented • material.io and

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

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

    Visual tweaks are ok • …but not for all • Behaviour changes • Incompatible semantics Flexibility! Flexibility!
  9. 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
  10. 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 🤌
  11. A design system is born • Designers hand over a

    spec yo, new specs! do this The Designer MY SPECS 🤌 👍
  12. 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 🤌 👍
  13. 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
  14. Step 1: no more Material • You don’t want Material

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

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

    • Good news! • Views: monolithic • Compose: modular • You can get rid of Material Views Compose
  17. 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") }
  18. 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") }
  19. 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
  20. 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
  21. 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
  22. Done! What now? • Four main components • We took

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

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

    LocalContentAlpha • LocalContentColor • LocalTextStyle So lonely :(
  25. 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 :(
  26. @Composable fun Text( ... ) MATERIAL @Composable fun BasicText( ...

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

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

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

    ... ) FOUNDATION @Composable fun Layout(…) UI @Composable fun ComposeNode(…) RUNTIME * not 100% accurate
  30. 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]
  31. 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!
  32. Composition locals fun MyTheme() { CompositionLocalProvider(LocalContentColor provides Color.Black) { BasicText(

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

    BasicText( text = "Hello!", style = TextStyle.Default.copy(color = LocalContentColor.current) ) } } Color.Black
  34. 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
  35. 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
  36. API design principles • Off i cial guidelines • Based

    on Kotlin conventions • Strongly recommended read • From years of experience • Helps with consistency • Meet users’ expectations
  37. 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 ) { // .. . }
  38. 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
  39. 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
  40. 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 }
  41. 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
  42. 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
  43. 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
  44. 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!
  45. Let’s pretend I work at Duolingo! I don’t! I love

    you Duo please don’t sue kthxbye
  46. 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, . .. ) { / / . .. } }
  47. @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 ) } } } }
  48. @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
  49. 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, ... ) }
  50. @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() ) }
  51. 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 = { .. . } ) }
  52. 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!
  53. 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!
  54. 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
  55. 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( ... ) } }
  56. @Composable fun ProgressBar( .. . ) { //... Canvas( modifier

    = modifier .progressSemantics(progress) .clip(RoundedCornerShape( .. . )) .defaultMinSize(minHeight = .. . ), ) { / /.. . drawRect(color = colors.track) updateProgressBounds( ... ) drawRoundRect( . .. ) updateHighlightBounds( .. . ) drawRoundRect( . .. ) } }
  57. @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...
  58. @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
  59. 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( ) { ... }
  60. 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( ) { ... }
  61. 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