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

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

    View Slide

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

    View Slide

  5. Material Design

    View 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 Slide

  7. Material is very cool!
    • Well documented


    • material.io and m3.material.io

    View 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 Slide

  9. Material is very cool!
    • Well documented


    • material.io and m3.material.io


    • Very flexible


    • Custom colours


    • Custom shapes


    • Custom typography
    Flexibility!

    View 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 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 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 Slide

  13. Other design


    systems

    View 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 Slide

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

    View 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 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 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 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 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 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 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 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 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 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 Slide

  26. Done! What now?
    • Four main components


    • We took Material out


    • Most Composables are in Material


    • Oops.
    runtime material
    foundation ui
    🫣

    View 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 Slide

  28. Not just composables
    • Ripples


    • Some useful APIs


    • LocalContentAlpha


    • LocalContentColor


    • LocalTextStyle
    So lonely :(

    View 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 Slide

  30. WE WILL REBUILD
    UPON FOUNDATION

    View Slide

  31. WE WILL REBUILD
    UPON FOUNDATION

    View Slide

  32. @Composable


    fun Text(
    ...
    )

    View Slide

  33. @Composable


    fun Text(
    ...
    )
    MATERIAL

    View Slide

  34. @Composable


    fun Text(
    ...
    )
    MATERIAL
    @Composable


    fun BasicText(
    ...
    )
    FOUNDATION

    View Slide

  35. @Composable


    fun Text(
    ...
    )
    MATERIAL
    @Composable


    fun BasicText(
    ...
    )
    FOUNDATION
    @Composable


    fun Layout(…)
    UI

    View Slide

  36. @Composable


    fun Text(
    ...
    )
    MATERIAL
    @Composable


    fun BasicText(
    ...
    )
    FOUNDATION
    @Composable


    fun Layout(…)
    UI

    View Slide

  37. @Composable


    fun Text(
    ...
    )
    MATERIAL
    @Composable


    fun BasicText(
    ...
    )
    FOUNDATION
    @Composable


    fun Layout(…)
    UI
    @Composable


    fun ComposeNode(…)
    RUNTIME
    * not 100% accurate

    View Slide

  38. @Composable


    fun MyText(
    ...
    )
    MY THEME
    @Composable


    fun BasicText(
    ...
    )
    FOUNDATION
    @Composable


    fun Layout(…)
    UI
    @Composable


    fun ComposeNode(…)
    RUNTIME
    * not 100% accurate

    View 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 Slide

  40. Theming


    themes


    composingly

    View 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 Slide

  42. Composition locals
    • Provide values


    • Implicit dependency


    • Useful for common “data”

    View Slide

  43. Composition locals


    fun MyTheme() {


    CompositionLocalProvider(LocalContentColor provides Color.Black) {


    BasicText(


    text = "Hello!",


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


    )


    }


    }
    Color.Black

    View 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 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 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 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 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 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 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 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 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 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 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 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 Slide

  56. Make your


    Composables

    View Slide

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

    View 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 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 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 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 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 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 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 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 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 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 Slide

  68. @Composable


    fun ProgressBar(
    ..
    .
    ) {


    //...


    Canvas(


    modifier = modifier


    .progressSemantics(progress)


    .clip(RoundedCornerShape(
    .. .
    ))


    .defaultMinSize(minHeight =
    .. .
    ),


    ) {

    /
    /..
    .

    drawRect(color = colors.track)


    updateProgressBounds(
    ...
    )


    drawRoundRect(
    .
    ..
    )


    updateHighlightBounds(
    .. .
    )


    drawRoundRect(
    .
    ..
    )


    }


    }

    View 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 Slide

  70. View Slide

  71. View Slide

  72. View Slide

  73. View Slide

  74. View Slide

  75. View Slide

  76. Sucks!

    View Slide

  77. View Slide

  78. clip()

    View Slide

  79. clip()

    View Slide

  80. clip()

    View Slide

  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

    View Slide

  82. We’re almost


    there…

    View Slide

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

    View Slide

  84. Cutting corners
    • Don’t overcomplicate things


    • YAGNI → drop it
    @Composable


    fun Button(
    ) {
    ...
    }

    View Slide

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

    View Slide

  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

    View Slide

  87. What we


    learnt today

    View Slide

  88. RECAP
    1. Study Material

    View Slide

  89. 1. Study Material
    2. Use Foundation
    RECAP

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  93. Questions?

    View Slide

  94. Compose


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

    View Slide

  95. Compose


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

    View Slide