Save 37% off PRO during our Black Friday Sale! »

Migrating Your Design System to Jetpack Compose

Migrating Your Design System to Jetpack Compose

Jetpack Compose represents a paradigm shift in how Android developers approach building their UI, but how do you get started, and how do you transition a complex app - and your team - to this new model? In this talk, Adam discusses how Cuvva has approached this migration, the tools that are available, and offers practical advice on the steps to take in your team.

15217910de00471b472fecae440c6daf?s=128

Adam Bennett

November 25, 2020
Tweet

Transcript

  1. Migrating your Design System to Jetpack Compose Adam Bennett |

    @iateyourmic | adambennett.dev
  2. • What is a design system? • Planning your approach

    • How to migrate • Tips, tricks and advice
  3. A design system is a collection of reusable components, guided

    by clear standards, that can be assembled together to build any number of applications
  4. None
  5. None
  6. None
  7. None
  8. None
  9. 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 } // ... }
  10. Planning your approach • Create a design app if you

    haven’t already
  11. :design

  12. :design :design-compose

  13. :design :design-compose

  14. Planning your approach • Create a design app if you

    haven’t already • Get your team onboard
  15. 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
  16. 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
  17. Porting Your Theme

  18. @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 ) }
  19. val lightPalette = lightColors( primary = // ..., primaryVariant =

    // ..., onPrimary = // ..., secondary = // ..., secondaryVariant = // ..., onSecondary = // ..., onSurface = // ..., onBackground = // ..., error = // ..., onError = // ... )
  20. val lightPalette = CuvvaColors( primaryFill = // ..., primaryFillFaded =

    // ..., secondaryFill = // ..., secondaryFillFaded = // ..., tertiaryFill = // ..., tertiaryFillFaded = // ..., surfaceFill = // ..., surfaceFillFaded = // ..., accentFill = // ..., accentFillFaded = // ..., blankFill = // ..., blankFillFaded = // ..., alertFill = // ..., alertFillFaded = // ..., // ... )
  21. @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 ) }
  22. object CuvvaTheme { @Composable val colors: CuvvaColors get() = AmbientCuvvaColors.current

    } private val AmbientCuvvaColors = staticAmbientOf<CuvvaColors> { error("No CuvvaColors provided") }
  23. @Composable fun CuvvaTheme( content: @Composable () -> Unit ) {

    Providers(AmbientCuvvaColors provides semanticColors) { MaterialTheme( colors = debugColors(), content = content, ) } }
  24. setContent { MaterialTheme { Button( color = MaterialTheme.colors.primary, // ...

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

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

    ) }
  27. Foundation UI Runtime Your Design System Material Design

  28. Material Design Foundation UI Runtime Your Design System

  29. Foundation UI Runtime Your Design System

  30. @Composable fun Text( text: String, modifier: Modifier = Modifier, color:

    Color = Color.Unspecified, // ... )
  31. Integrating Compose

  32. :design :design-compose :button-compose

  33. <co.cuvva.design.title.sub.button.view.TitleSubButtonView android:id="@+id/help_button" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/divider" app:layout_constraintTop_toBottomOf="@id/resolve_payment" app:icon="@drawable/ic_phone_outline_24dp" app:mode="destructive" />

    <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/help_button" /> </androidx.constraintlayout.widget.ConstraintLayout>
  34. 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<ComposeView>(R.id.compose_view) init { inflate(R.layout.view_subscription) composeView.setContent { CuvvaTheme { CardButton( // ... ) } } }
  35. setContent { Column { Text(text = "Hello, world!") AndroidView(::MyButtonView) {

    button -> button.text = "Click me" } } }
  36. Which strategy to use?

  37. Testing

  38. You wouldn’t Refactor legacy code Without a test harness

  39. github.com/Karumi/Shot

  40. class SimpleUiTest : ScreenshotTest { @get:Rule val composeTestRule = createComposeRule()

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

    @Test fun example_test() { composeTestRule.setContent { MyTheme { MyButton(text = "Hello, world!") } } compareScreenshot(composeRule) } }
  42. 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) } }
  43. Tip: Composable are cheap

  44. @Composable fun MyButton( text: String, backgroundColor: Color, textColor: Color )

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

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

    textColor = CuvvaTheme.colors.textOnSurface ) }
  47. data class CuvvaColors( val textColors: TextColors(...), val backgroundColors: BackgroundColors(...), //

    ... ) setContent { MyButton( text = "Hello, world!", backgroundColor = CuvvaTheme.colors.backgroundColors.primary, textColor = CuvvaTheme.colors.textColors.textOnSurface ) }
  48. @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(...) }
  49. object MyButton { @Composable fun Primary(...) @Composable fun Secondary(...) }

    setContent { Button.Primary(...) }
  50. Heavily nested layouts are fine if you’re not doing any

    measuring
  51. Tip: Metrics

  52. Metrics give you concrete proof of progress

  53. • Layouts • Custom Views • Themes & Styles •

    Colors • Dimensions Compose
  54. • Worth investigating now - in parallel • Approach it

    with a plan • Use it to overhaul your design system
  55. • Still in the early stages • Learnt a lot

    about big migrations • We’re really enjoying it! • Expect more content from us • github.com/cuvva
  56. Thank you! Adam Bennett | @iateyourmic | adambennett.dev