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. Compose


    beyond Material
    Sebastiano Poggi
    @seebrock3r

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  5. Material Design

    View full-size slide

  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

    View full-size slide

  7. Material is very cool!
    • Well documented


    • material.io and m3.material.io

    View full-size slide

  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!

    View full-size slide

  9. Material is very cool!
    • Well documented


    • material.io and m3.material.io


    • Very flexible


    • Custom colours


    • Custom shapes


    • Custom typography
    Flexibility!

    View full-size slide

  10. Material is very cool!
    • Well documented


    • material.io and m3.material.io


    • Very flexible


    • Custom colours


    • Custom shapes


    • Custom typography
    Flexibility!
    Flexibility!
    Flexibility!

    View full-size slide

  11. Material is very cool!
    • Enough for most use-cases


    • Visual tweaks are ok


    • …but not for all


    • Behaviour changes


    • Incompatible semantics
    Flexibility!
    Flexibility!

    View full-size slide

  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

    View full-size slide

  13. Other design


    systems

    View full-size slide

  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
    🤌

    View full-size slide

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

    View full-size slide

  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
    🤌
    👍

    View full-size slide

  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

    View full-size slide

  18. Step 1: no more Material
    • You don’t want Material


    • Good news!


    • Views: monolithic


    • Compose: modular


    • You can get rid of Material
    Views

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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")


    }

    View full-size slide

  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")


    }

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  26. Done! What now?
    • Four main components


    • We took Material out


    • Most Composables are in Material


    • Oops.
    runtime material
    foundation ui
    🫣

    View full-size slide

  27. Text
    Button
    Card
    Checkbox
    AlertDialog
    Divider
    RadioButton
    CircularProgressIndicator
    LinearProgressIndicator
    Snackbar
    Switch
    Tab
    TextField
    Snackbar
    Slider
    BottomDrawer TopAppBar
    FloatingActionButton
    Chip
    Surface Icon
    MaterialTheme NavigationRail

    View full-size slide

  28. Not just composables
    • Ripples


    • Some useful APIs


    • LocalContentAlpha


    • LocalContentColor


    • LocalTextStyle
    So lonely :(

    View full-size slide

  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 :(

    View full-size slide

  30. WE WILL REBUILD
    UPON FOUNDATION

    View full-size slide

  31. WE WILL REBUILD
    UPON FOUNDATION

    View full-size slide

  32. @Composable


    fun Text(
    ...
    )

    View full-size slide

  33. @Composable


    fun Text(
    ...
    )
    MATERIAL

    View full-size slide

  34. @Composable


    fun Text(
    ...
    )
    MATERIAL
    @Composable


    fun BasicText(
    ...
    )
    FOUNDATION

    View full-size slide

  35. @Composable


    fun Text(
    ...
    )
    MATERIAL
    @Composable


    fun BasicText(
    ...
    )
    FOUNDATION
    @Composable


    fun Layout(…)
    UI

    View full-size slide

  36. @Composable


    fun Text(
    ...
    )
    MATERIAL
    @Composable


    fun BasicText(
    ...
    )
    FOUNDATION
    @Composable


    fun Layout(…)
    UI

    View full-size slide

  37. @Composable


    fun Text(
    ...
    )
    MATERIAL
    @Composable


    fun BasicText(
    ...
    )
    FOUNDATION
    @Composable


    fun Layout(…)
    UI
    @Composable


    fun ComposeNode(…)
    RUNTIME
    * not 100% accurate

    View full-size slide

  38. @Composable


    fun MyText(
    ...
    )
    MY THEME
    @Composable


    fun BasicText(
    ...
    )
    FOUNDATION
    @Composable


    fun Layout(…)
    UI
    @Composable


    fun ComposeNode(…)
    RUNTIME
    * not 100% accurate

    View full-size slide

  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]

    View full-size slide

  40. Theming


    themes


    composingly

    View full-size slide

  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!

    View full-size slide

  42. Composition locals
    • Provide values


    • Implicit dependency


    • Useful for common “data”

    View full-size slide

  43. Composition locals


    fun MyTheme() {


    CompositionLocalProvider(LocalContentColor provides Color.Black) {


    BasicText(


    text = "Hello!",


    style = TextStyle.Default.copy(color = LocalContentColor.current)


    )


    }


    }
    Color.Black

    View full-size slide

  44. Composition locals
    @Composable


    fun MyTheme() {


    CompositionLocalProvider(LocalContentColor provides Color.Black) {


    BasicText(


    text = "Hello!",


    style = TextStyle.Default.copy(color = LocalContentColor.current)


    )


    }


    }
    Color.Black


    View full-size slide

  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


    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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


    ) {

    // ..
    .

    }

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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


    }

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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!

    View full-size slide

  56. Make your


    Composables

    View full-size slide

  57. Let’s pretend
    I work at Duolingo!
    I don’t!
    I love you Duo please don’t sue kthxbye

    View full-size slide

  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,


    .
    ..

    ) {

    /
    / .
    ..


    }


    }

    View full-size slide

  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


    )


    }


    }


    }


    }

    View full-size slide

  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

    View full-size slide

  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,
    ...
    )


    }

    View full-size slide

  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()


    )


    }

    View full-size slide

  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 = {
    ..
    .
    }


    )


    }

    View full-size slide

  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!

    View full-size slide

  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!

    View full-size slide

  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

    View full-size slide

  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(
    ...
    )


    }


    }

    View full-size slide

  68. @Composable


    fun ProgressBar(
    ..
    .
    ) {


    //...


    Canvas(


    modifier = modifier


    .progressSemantics(progress)


    .clip(RoundedCornerShape(
    .. .
    ))


    .defaultMinSize(minHeight =
    .. .
    ),


    ) {

    /
    /..
    .

    drawRect(color = colors.track)


    updateProgressBounds(
    ...
    )


    drawRoundRect(
    .
    ..
    )


    updateHighlightBounds(
    .. .
    )


    drawRoundRect(
    .
    ..
    )


    }


    }

    View full-size slide

  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...

    View full-size slide

  70. @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

    View full-size slide

  71. We’re almost


    there…

    View full-size slide

  72. 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(
    ) {
    ...
    }

    View full-size slide

  73. Cutting corners
    • Don’t overcomplicate things


    • YAGNI → drop it
    @Composable


    fun Button(
    ) {
    ...
    }

    View full-size slide

  74. 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(
    ) {
    ...
    }

    View full-size slide

  75. 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

    View full-size slide

  76. What we


    learnt today

    View full-size slide

  77. RECAP
    1. Study Material

    View full-size slide

  78. 1. Study Material
    2. Use Foundation
    RECAP

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  82. Compose


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

    View full-size slide

  83. Compose


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

    View full-size slide