Slide 1

Slide 1 text

Migrating your Design System to Jetpack Compose Adam Bennett | @iateyourmic | adambennett.dev

Slide 2

Slide 2 text

• What is a design system? • Planning your approach • How to migrate • Tips, tricks and advice

Slide 3

Slide 3 text

A design system is a collection of reusable components, guided by clear standards, that can be assembled together to build any number of applications

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

class ButtonView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) { enum class Mode { GO_ELEVATED, GO_ELEVATED_MUTED, SECONDARY_ELEVATED, SECONDARY_ELEVATED_MUTED, DESTRUCTIVE_ELEVATED, DESTRUCTIVE_ELEVATED_MUTED, GO_UNELEVATED, GO_UNELEVATED_MUTED, SECONDARY_UNELEVATED, SECONDARY_UNELEVATED_MUTED, DESTRUCTIVE_UNELEVATED, DESTRUCTIVE_UNELEVATED_MUTED, OUTLINED, TEXT } // ... }

Slide 10

Slide 10 text

Planning your approach • Create a design app if you haven’t already

Slide 11

Slide 11 text

:design

Slide 12

Slide 12 text

:design :design-compose

Slide 13

Slide 13 text

:design :design-compose

Slide 14

Slide 14 text

Planning your approach • Create a design app if you haven’t already • Get your team onboard

Slide 15

Slide 15 text

Hack Day • A chance for everyone to finally explore Compose • Pool together learning resources • Spend the morning learning at your own speed • Spend the afternoon hacking • Show and tell the next day • Discuss pros, cons, likes and dislikes in the team • Credit to SnappMobile

Slide 16

Slide 16 text

Planning your approach • Create a design app if you haven’t already • Get your team onboard! • Think about decoupling your components • Think about your data flow • Consider testing • Consider gathering metrics

Slide 17

Slide 17 text

Porting Your Theme

Slide 18

Slide 18 text

@Composable fun MyTheme( isDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { val myColors = if (isDarkTheme) lightColors(...) else darkColors(...) MaterialTheme( colors = myColors, content = content, typography = Typography, shapes = Shapes ) }

Slide 19

Slide 19 text

val lightPalette = lightColors( primary = // ..., primaryVariant = // ..., onPrimary = // ..., secondary = // ..., secondaryVariant = // ..., onSecondary = // ..., onSurface = // ..., onBackground = // ..., error = // ..., onError = // ... )

Slide 20

Slide 20 text

val lightPalette = CuvvaColors( primaryFill = // ..., primaryFillFaded = // ..., secondaryFill = // ..., secondaryFillFaded = // ..., tertiaryFill = // ..., tertiaryFillFaded = // ..., surfaceFill = // ..., surfaceFillFaded = // ..., accentFill = // ..., accentFillFaded = // ..., blankFill = // ..., blankFillFaded = // ..., alertFill = // ..., alertFillFaded = // ..., // ... )

Slide 21

Slide 21 text

@Composable fun MyTheme( isDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { val myColors = if (isDarkTheme) MyColors(...) else MyColors(…) MaterialTheme( colors = myColors, content = content, typography = Typography, shapes = Shapes ) }

Slide 22

Slide 22 text

object CuvvaTheme { @Composable val colors: CuvvaColors get() = AmbientCuvvaColors.current } private val AmbientCuvvaColors = staticAmbientOf { error("No CuvvaColors provided") }

Slide 23

Slide 23 text

@Composable fun CuvvaTheme( content: @Composable () -> Unit ) { Providers(AmbientCuvvaColors provides semanticColors) { MaterialTheme( colors = debugColors(), content = content, ) } }

Slide 24

Slide 24 text

setContent { MaterialTheme { Button( color = MaterialTheme.colors.primary, // ... ) } }

Slide 25

Slide 25 text

setContent { CuvvaTheme { Button( color = CuvvaTheme.colors.goAction, // ... ) } }

Slide 26

Slide 26 text

@Composable fun GoActionButton() { Button( color = CuvvaTheme.colors.goAction, // ... ) }

Slide 27

Slide 27 text

Foundation UI Runtime Your Design System Material Design

Slide 28

Slide 28 text

Material Design Foundation UI Runtime Your Design System

Slide 29

Slide 29 text

Foundation UI Runtime Your Design System

Slide 30

Slide 30 text

@Composable fun Text( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, // ... )

Slide 31

Slide 31 text

Integrating Compose

Slide 32

Slide 32 text

:design :design-compose :button-compose

Slide 33

Slide 33 text

Slide 34

Slide 34 text

class SubscriptionView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) { // ... private val composeView by view(R.id.compose_view) init { inflate(R.layout.view_subscription) composeView.setContent { CuvvaTheme { CardButton( // ... ) } } }

Slide 35

Slide 35 text

setContent { Column { Text(text = "Hello, world!") AndroidView(::MyButtonView) { button -> button.text = "Click me" } } }

Slide 36

Slide 36 text

Which strategy to use?

Slide 37

Slide 37 text

Testing

Slide 38

Slide 38 text

You wouldn’t Refactor legacy code Without a test harness

Slide 39

Slide 39 text

github.com/Karumi/Shot

Slide 40

Slide 40 text

class SimpleUiTest : ScreenshotTest { @get:Rule val composeTestRule = createComposeRule() @Test fun example_test() { composeTestRule.setContent { MyTheme { AndroidView(::MyButtonView) { button -> button.text = "Hello, world!" } } } compareScreenshot(composeRule) } }

Slide 41

Slide 41 text

class SimpleUiTest : ScreenshotTest { @get:Rule val composeTestRule = createComposeRule() @Test fun example_test() { composeTestRule.setContent { MyTheme { MyButton(text = "Hello, world!") } } compareScreenshot(composeRule) } }

Slide 42

Slide 42 text

class SimpleUiTest : ScreenshotTest { @get:Rule val composeTestRule = createComposeRule() @Test fun example_test_with_very_long_text() { composeTestRule.setContent { MyTheme { MyButton(text = "Hello, world! Are you still there?”) } } compareScreenshot(composeRule) } }

Slide 43

Slide 43 text

Tip: Composable are cheap

Slide 44

Slide 44 text

@Composable fun MyButton( text: String, backgroundColor: Color, textColor: Color ) { Button(...) }

Slide 45

Slide 45 text

setContent { MyButton( text = "Hello, world!", backgroundColor = CuvvaTheme.colors.primary, textColor = CuvvaTheme.colors.textOnSurface ) }

Slide 46

Slide 46 text

setContent { MyButton( text = "Hello, world!", backgroundColor = CuvvaTheme.colors.textOnSurface, textColor = CuvvaTheme.colors.textOnSurface ) }

Slide 47

Slide 47 text

data class CuvvaColors( val textColors: TextColors(...), val backgroundColors: BackgroundColors(...), // ... ) setContent { MyButton( text = "Hello, world!", backgroundColor = CuvvaTheme.colors.backgroundColors.primary, textColor = CuvvaTheme.colors.textColors.textOnSurface ) }

Slide 48

Slide 48 text

@Composable fun PrimaryButton( text: String ) { MyButton( text = text, backgroundColor = CuvvaTheme.colors.backgroundColors.primary, textColor = CuvvaTheme.colors.textColors.textOnSurface ) } @Composable private fun MyButton( text: String, backgroundColor: Color, textColor: Color ) { Button(...) }

Slide 49

Slide 49 text

object MyButton { @Composable fun Primary(...) @Composable fun Secondary(...) } setContent { Button.Primary(...) }

Slide 50

Slide 50 text

Heavily nested layouts are fine if you’re not doing any measuring

Slide 51

Slide 51 text

Tip: Metrics

Slide 52

Slide 52 text

Metrics give you concrete proof of progress

Slide 53

Slide 53 text

• Layouts • Custom Views • Themes & Styles • Colors • Dimensions Compose

Slide 54

Slide 54 text

• Worth investigating now - in parallel • Approach it with a plan • Use it to overhaul your design system

Slide 55

Slide 55 text

• Still in the early stages • Learnt a lot about big migrations • We’re really enjoying it! • Expect more content from us • github.com/cuvva

Slide 56

Slide 56 text

Thank you! Adam Bennett | @iateyourmic | adambennett.dev