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

Migrating Your Design System to Jetpack Compose

Adam Bennett
November 25, 2020

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.

Adam Bennett

November 25, 2020
Tweet

More Decks by Adam Bennett

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  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

    View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. View Slide

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

    View Slide

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

    View Slide

  11. :design

    View Slide

  12. :design
    :design-compose

    View Slide

  13. :design
    :design-compose

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  17. Porting Your Theme

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  27. Foundation
    UI
    Runtime
    Your Design System
    Material Design

    View Slide

  28. Material Design
    Foundation
    UI
    Runtime
    Your Design System

    View Slide

  29. Foundation
    UI
    Runtime
    Your Design System

    View Slide

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

    View Slide

  31. Integrating Compose

    View Slide

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

    View Slide

  33. 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" />
    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" />

    View Slide

  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(R.id.compose_view)
    init {
    inflate(R.layout.view_subscription)
    composeView.setContent {
    CuvvaTheme {
    CardButton(
    // ...
    )
    }
    }
    }

    View Slide

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

    View Slide

  36. Which strategy to use?

    View Slide

  37. Testing

    View Slide

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

    View Slide

  39. github.com/Karumi/Shot

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  43. Tip: Composable are cheap

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  51. Tip: Metrics

    View Slide

  52. Metrics give you concrete proof of progress

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide