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

Composing a Design System

123f264bb3f071d7422c4ca8e83f27a8?s=47 Anton Shilov
February 24, 2021

Composing a Design System

Jetpack Compose - the promise of bringing our app to life with dramatically less code, interactive tools, and intuitive Kotlin APIs. how would we be able to migrate the existing design system to Jetpack Compose? What are the key steps to follow for successful integration with the existing codebase?

123f264bb3f071d7422c4ca8e83f27a8?s=128

Anton Shilov

February 24, 2021
Tweet

Transcript

  1. Anton Shilov Composing a Design System 1

  2. Bumble Badoo 2

  3. Private and con fi dential ~1 M LoC ~500 0

    UI-tests ~10 0 UI-components 3
  4. Private and con fi dential 1. Design syste m •

    Token s • Component s • Pattern s 2. Android View interoperabilit y 3. Testin g 4. Integration 4
  5. Private and con fi dential Jetpack Compose — declarative UI

    framework for Kotlin 5
  6. Private and con fi dential 6 @Composable fun HelloCompose() {

    Column{ Text(text = "Hi there!") Text(text = "Welcome to the talk") Button(onClick = {}) { Text(text = "Let's start!") } } }
  7. Private and con fi dential 7 @Composable fun HelloCompose() {

    Column{ Text(text = "Hi there!") Text(text = "Welcome to the talk") Button(onClick = {}) { Text(text = "Let's start!") } } }
  8. Private and con fi dential 8 @Composable fun HelloCompose() {

    Column{ Text(text = "Hi there!") Text(text = "Welcome to the talk") Button(onClick = {}) { Text(text = "Let's start!") } } }
  9. Private and con fi dential 9 @Composable fun HelloCompose() {

    Column{ Text(text = "Hi there!") Text(text = "Welcome to the talk") Button(onClick = {}) { Text(text = "Let's start!") } } }
  10. Private and con fi dential • Future of Android U

    I • Faster developmen t • Easy integration with our architecture — MV I • Our own declarative UI framewor k • Easy to implement design systems Why Compose? 10
  11. Private and con fi dential Goal 🎯 Use Compose once

    the release is stable 11
  12. Private and con fi dential • How to create a

    design system ? • How to integrate with the current UI ? • How to test ? • How to integrate within the business logic? Questions 12
  13. Design System 13

  14. Private and con fi dential Design system — set of

    rules and tools for UI design 14
  15. Private and con fi dential 15 Text styles Text styles

    Text styles Text styles
  16. Private and con fi dential Dimensions 16 Colors Design tokens

  17. Private and con fi dential Icons 17

  18. Private and con fi dential Components 18

  19. Private and con fi dential Patterns 19

  20. Private and con fi dential 20

  21. Private and con fi dential 21 MatchPhotos Text - H1

    Button - Filled - White Text - P1 CtaBox ic badge-feature-match
  22. Private and con fi dential data class TextStyle( val color:

    Color = Color.Unspecified, val fontSize: TextUnit = TextUnit.Inherit, val fontWeight: FontWeight? = null, val fontStyle: FontStyle? = null, val fontSynthesis: FontSynthesis? = null, val fontFamily: FontFamily? = null, val fontFeatureSettings: String? = null, val letterSpacing: TextUnit = TextUnit.Inherit, val baselineShift: BaselineShift? = null, val textGeometricTransform: TextGeometricTransform? = null, val localeList: LocaleList? = null, val background: Color = Color.Unspecified, val textDecoration: TextDecoration? = null, val shadow: Shadow? = null, val textAlign: TextAlign? = null, val textDirection: TextDirection? = null, val lineHeight: TextUnit = TextUnit.Inherit, val textIndent: TextIndent? = null ) 22
  23. Private and con fi dential 23 H1 = TextStyle( fontFamily

    = FontFamily.Default, fontWeight = FontWeight.Medium, fontSize = 24.sp, lineHeight = 28.sp )
  24. Private and con fi dential 24 data class Typography( val

    H1: TextStyle, val H2: TextStyle, val Action: TextStyle, val Title: TextStyle, val P1: TextStyle, val P2: TextStyle, val P3: TextStyle )
  25. Private and con fi dential 25 colorResource(id = R.color.black) Color(0xFF783bf9)

  26. Private and con fi dential 26 data class Palette( val

    primary: Color, val matchPhotosBorder: Color, //. . . )
  27. Private and con fi dential 27 val sizeDp = 16.dp

    val sizeSp = 16.sp dimensionResource(id = R.dimen.size_lg)
  28. Private and con fi dential 28 inline val Int.dp: Dp

    get() = Dp(value = this)
  29. Private and con fi dential 29 data class DimensionsTokens( val

    tokenMatchPhotosPhotoSize: Dp, val tokenButtonHeight: Dp, val tokenButtonIconSize: Dp, val tokenButtonIconTextSpacing: Dp, val tokenButtonDisabledOpacity: Float, val tokenButtonStrokeBorderWidth: Dp, val tokenMatchPhotosBorderWidth: Dp, //. . . )
  30. Icons 30 ImageVector ImageBitmap Drawable

  31. Private and con fi dential 31 imageResource(id = R.drawable.photo) vectorResource(id

    = R.drawable.ic_match)
  32. Private and con fi dential 32 val Icons.Filled.TestVector: ImageVector get()

    = materialIcon(name = "Filled.TestVector") { materialPath(fillAlpha = 0.8f) { moveTo(20.0f, 10.0f) lineToRelative(0.0f, 10.0f) lineToRelative(-10.0f, 0.0f) close() } group { materialPath(pathFillType = EvenOdd) { moveTo(0.0f, 10.0f) lineToRelative(-10.0f, 0.0f) close() } } }
  33. Styles + Tokens = Theme 33

  34. Private and con fi dential 34 App Scaffol d Toolba

    r Tex t Conten t Hear t
  35. Private and con fi dential 35 App Scaffol d Toolba

    r Tex t Conten t Hear t App Scaffol d Toolba r Tex t Conten t Hear t
  36. Private and con fi dential 36 val LocalHeartColor = compositionLocalOf<Color>()

    @Composable fun HeartsScreen() { Providers( LocalHeartColor provides Color.Red ) { LocalHeartColor.current // I can access to the color her } }
  37. Private and con fi dential 37 val LocalHeartColor = compositionLocalOf<Color>()

    @Composable fun HeartsScreen() { Providers( LocalHeartColor provides Color.Red ) { LocalHeartColor.current // I can access to the color her } }
  38. Private and con fi dential 38 val LocalHeartColor = compositionLocalOf<Color>()

    @Composable fun HeartsScreen() { Providers( LocalHeartColor provides Color.Red ) { LocalHeartColor.current // I can access to the color her } }
  39. Private and con fi dential 39 val LocalHeartColor = compositionLocalOf<Color>()

    @Composable fun HeartsScreen() { Providers( LocalHeartColor provides Color.Red ) { LocalHeartColor.current // I can access to the color her } }
  40. Private and con fi dential 40 Key Value LocalHeartColor Color.Red

    LocalContext Context LocalLifecycleOwner LifecycleOwner
  41. Private and con fi dential 41 val LocalTypography = staticCompositionLocalOf<Typography>()

    @Composable val TextStyles: Typography get() = LocalTypography.current TextStyles.H1 // LocalTypography.current.H1
  42. Private and con fi dential 42 val LocalTypography = staticCompositionLocalOf<Typography>()

    @Composable val TextStyles: Typography get() = LocalTypography.current TextStyles.H1 // LocalTypography.current.H1
  43. Private and con fi dential 43 val LocalTypography = staticCompositionLocalOf<Typography>()

    @Composable val TextStyles: Typography get() = LocalTypography.current TextStyles.H1 // LocalTypography.current.H1
  44. Private and con fi dential 44 val LocalTypography = staticCompositionLocalOf<Typography>()

    @Composable val TextStyles: Typography get() = LocalTypography.current TextStyles.H1 // LocalTypography.current.H1
  45. Private and con fi dential 45 @Composable fun Theme( typography:

    Typography, colors: Palette, dimensions: DimensionsTokens, content: @Composable () -> Unit ) { Providers( LocalTypography provides typography, LocalColors provides colors, LocalDimensions provides dimensions, ) { content() } }
  46. Private and con fi dential 46 @Composable fun Theme( typography:

    Typography, colors: Palette, dimensions: DimensionsTokens, content: @Composable () -> Unit ) { Providers( LocalTypography provides typography, LocalColors provides colors, LocalDimensions provides dimensions, ) { content() } }
  47. Private and con fi dential 47 @Composable fun Theme( typography:

    Typography, colors: Palette, dimensions: DimensionsTokens, content: @Composable () -> Unit ) { Providers( LocalTypography provides typography, LocalColors provides colors, LocalDimensions provides dimensions, ) { content() // TextStyles.H1 } }
  48. Private and con fi dential Composition Local is not a

    DI framework! 48
  49. Private and con fi dential 1. Design syste m •

    Token s • Component s • Pattern s 2. Android View intero p 3. Testin g 4. Integration 49
  50. Private and con fi dential 50 MatchPhotos Button - Filled

    - White
  51. Button 51

  52. Private and con fi dential 52 @Composable fun MaterialButton() {

    Button( onClick = {}, content = { Text(text = "Hello Compose”) } ) }
  53. Private and con fi dential 53 @Composable fun SlotApi() {

    Button( onClick = {}, backgroundColor = Color.Red ) { Button( onClick = {}, backgroundColor = Color.Green ) { Button( onClick = {}, backgroundColor = Color.Blue ) { Text(text = "We need to go deeper") } } } }
  54. Private and con fi dential 54

  55. Private and con fi dential 55 Filled Stroke Transparent

  56. Private and con fi dential 56 @Composable fun Button( onClick:

    () -> Unit, text: String, modifier: Modifier = Modifier, color: Color = Colors.primary, contentColor: Color = Color.White, icon: ImageVector? = null, enabled: Boolean = true )
  57. Private and con fi dential 57 @Composable fun Button( onClick:

    () -> Unit, text: String, modifier: Modifier = Modifier, color: Color = Colors.primary, contentColor: Color = Color.White, icon: ImageVector? = null, enabled: Boolean = true )
  58. Private and con fi dential 58 @Composable fun Button( onClick:

    () -> Unit, text: String, modifier: Modifier = Modifier, color: Color = Colors.primary, contentColor: Color = Color.White, icon: ImageVector? = null, enabled: Boolean = true )
  59. Private and con fi dential 59 @Composable fun Button( onClick:

    () -> Unit, text: String, modifier: Modifier = Modifier, color: Color = Colors.primary, contentColor: Color = Color.White, icon: ImageVector? = null, enabled: Boolean = true )
  60. Private and con fi dential 60 @Composable private fun ButtonSkeleton(

    onClick: () -> Unit, text: String, modifier: Modifier = Modifier, enabled: Boolean = true, shape: Shape = RoundedCornerShape(Dimensions.tokenButtonCornerRadius), border: BorderStroke? = null, backgroundColor: Color, contentColor: Color = Color.White, contentPadding: PaddingValues = PaddingValues( start = Dimensions.tokenButtonPaddingHorizontal, end = Dimensions.tokenButtonPaddingHorizontal, top = Dimensions.tokenButtonPaddingVertical, bottom = Dimensions.tokenButtonPaddingVertical ), height: Dp = Dimensions.tokenButtonHeight, icon: ImageVector?, )
  61. Private and con fi dential 61 @Composable private fun ButtonSkeleton(

    onClick: () -> Unit, text: String, modifier: Modifier = Modifier, enabled: Boolean = true, shape: Shape = RoundedCornerShape(Dimensions.tokenButtonCornerRadius), border: BorderStroke? = null, backgroundColor: Color, contentColor: Color = Color.White, contentPadding: PaddingValues = PaddingValues( start = Dimensions.tokenButtonPaddingHorizontal, end = Dimensions.tokenButtonPaddingHorizontal, top = Dimensions.tokenButtonPaddingVertical, bottom = Dimensions.tokenButtonPaddingVertical ), height: Dp = Dimensions.tokenButtonHeight, icon: ImageVector? )
  62. Private and con fi dential 62 FilledButton ButtonSkeleto n Surfac

    e Ro w Ico n Tex t
  63. Private and con fi dential 63 FilledButton ButtonSkeleto n Surfac

    e Ro w Ico n Tex t - Public API
  64. Private and con fi dential 64 FilledButton ButtonSkeleto n Surfac

    e Ro w Ico n Tex t - private generic button
  65. Private and con fi dential 65 FilledButton ButtonSkeleto n Surfac

    e Ro w Ico n Tex t - background & clicks
  66. Private and con fi dential 66 FilledButton ButtonSkeleto n Surfac

    e Ro w Ico n Tex t - content layout
  67. Private and con fi dential 67 FilledButton ButtonSkeleto n Surfac

    e Ro w Ico n Tex t - content
  68. Private and con fi dential 68 Surface( shape = shape,

    color = backgroundColor, contentColor = contentColor, border = border, modifier = modifier.clickable( onClick = onClick, enabled = enabled ).alpha( if (enabled) 1f else Dimensions.tokenButtonDisabledOpacity ) )
  69. Private and con fi dential 69 Surface( shape = shape,

    color = backgroundColor, contentColor = contentColor, border = border, modifier = modifier.clickable( onClick = onClick, enabled = enabled ).alpha( if (enabled) 1f else Dimensions.tokenButtonDisabledOpacity ) )
  70. Private and con fi dential 70 Surface( shape = shape,

    color = backgroundColor, contentColor = contentColor, border = border, modifier = modifier.clickable( onClick = onClick, enabled = enabled ).alpha( if (enabled) 1f else Dimensions.tokenButtonDisabledOpacity ) )
  71. Private and con fi dential 71 Surface( shape = shape,

    color = backgroundColor, contentColor = contentColor, border = border, modifier = modifier.clickable( onClick = onClick, enabled = enabled ).alpha( if (enabled) 1f else Dimensions.tokenButtonDisabledOpacity ) )
  72. Private and con fi dential 72 Row( Modifier .defaultMinSizeConstraints(minHeight =

    height) .padding(contentPadding), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, )
  73. Private and con fi dential 73 if (icon != null)

    { Icon( modifier = Modifier.size(Dimensions.tokenButtonIconSize), imageVector = icon, tint = contentColor ) Spacer(modifier = Modifier.width(Dimensions.tokenButtonIconTextSpacing)) } Text( text = text, style = TextStyles.Action, color = contentColor, maxLines = 1, overflow = TextOverflow.Ellipsis )
  74. Private and con fi dential 74 if (icon != null)

    { Icon( modifier = Modifier.size(Dimensions.tokenButtonIconSize), imageVector = icon, tint = contentColor ) Spacer(modifier = Modifier.width(Dimensions.tokenButtonIconTextSpacing)) } Text( text = text, style = TextStyles.Action, color = contentColor, maxLines = 1, overflow = TextOverflow.Ellipsis )
  75. Private and con fi dential 75 if (icon != null)

    { Icon( modifier = Modifier.size(Dimensions.tokenButtonIconSize), imageVector = icon, tint = contentColor ) Spacer(modifier = Modifier.width(Dimensions.tokenButtonIconTextSpacing)) } Text( text = text, style = TextStyles.Action, color = contentColor, maxLines = 1, overflow = TextOverflow.Ellipsis )
  76. Private and con fi dential 76 @Composable fun Button( onClick:

    () -> Unit, text: String, modifier: Modifier = Modifier, color: Color = Colors.primary, contentColor: Color = Color.White, icon: ImageVector? = null, enabled: Boolean = true ) { ButtonSkeleton( onClick = onClick, modifier = modifier, backgroundColor = color, contentColor = contentColor, enabled = enabled, icon = icon, text = text ) }
  77. MatchPhotos 77

  78. Private and con fi dential 78 @Composable fun MatchPhotos( leftPhoto:

    ImageBitmap, rightPhoto: ImageBitmap, badge: ImageVector, modifier: Modifier = Modifier )
  79. Private and con fi dential 79 MatchPhotos Colum n Ro

    w Phot o Phot o Badg e
  80. Private and con fi dential 80 MatchPhotos Colum n Ro

    w Phot o - vertical linear layout Phot o Badg e
  81. Private and con fi dential 81 MatchPhotos Colum n Ro

    w Phot o - horizontal linear layout Phot o Badg e
  82. Private and con fi dential 82 MatchPhotos Colum n Ro

    w Phot o Phot o Badg e - content
  83. Private and con fi dential 83 Image( asset = rightPhoto,

    modifier = Modifier .graphicsLayer( rotationZ = rotationDegrees, translationX = rightTranslateX, translationY = rightTranslateY ) .zIndex(rightZIndex) .size(photoSize) .clip(photoShape) .border( width = borderWidth, Colors.matchPhotosBorder, photoShape ) )
  84. Private and con fi dential 84 Image( asset = rightPhoto,

    modifier = Modifier .graphicsLayer( rotationZ = rotationDegrees, translationX = rightTranslateX, translationY = rightTranslateY ) .zIndex(rightZIndex) .size(photoSize) .clip(photoShape) .border( width = borderWidth, Colors.matchPhotosBorder, photoShape ) )
  85. Private and con fi dential 85 Image( asset = rightPhoto,

    modifier = Modifier .graphicsLayer( rotationZ = rotationDegrees, translationX = rightTranslateX, translationY = rightTranslateY ) .zIndex(rightZIndex) .size(photoSize) .clip(photoShape) .border( width = borderWidth, Colors.matchPhotosBorder, photoShape ) )
  86. Private and con fi dential 86 Image( asset = rightPhoto,

    modifier = Modifier .graphicsLayer( rotationZ = rotationDegrees, translationX = rightTranslateX, translationY = rightTranslateY ) .zIndex(rightZIndex) .size(photoSize) .clip(photoShape) .border( width = borderWidth, Colors.matchPhotosBorder, photoShape ) )
  87. Private and con fi dential 87 Image( asset = rightPhoto,

    modifier = Modifier .graphicsLayer( rotationZ = rotationDegrees, translationX = rightTranslateX, translationY = rightTranslateY ) .zIndex(rightZIndex) .size(photoSize) .clip(photoShape) .border( width = borderWidth, Colors.matchPhotosBorder, photoShape ) )
  88. Private and con fi dential 88 Row { Image( asset

    = leftPhoto, ... ) Image( asset = rightPhoto, ... ) }
  89. Private and con fi dential 89 Column{ Row { Image()

    Image() } Image( imageVector = badge, modifier = Modifier .size(iconSize) .align(Alignment.CenterHorizontally) .shadow( elevation = badgeElevation, shape = CircleShape ) ) }
  90. Private and con fi dential 90 CtaBox

  91. Private and con fi dential 91 @Composable fun CtaBox( media:

    @Composable () - > Unit, header: @Composable () -> Unit, content: @Composable () - > Unit, buttons: @Composable() -> Unit, modifier: Modifier = Modifier )
  92. Private and con fi dential 92 Column( verticalArrangement = Arrangement.Center,

    horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.padding(Spacing.xlg) ) { media() Spacer(modifier = Modifier.size(Spacing.xlg)) header() Spacer(modifier = Modifier.size(Spacing.md)) content() Spacer(modifier = Modifier.size(Spacing.xlg)) buttons() }
  93. Private and con fi dential 93 Column( verticalArrangement = Arrangement.Center,

    horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.padding(Spacing.xlg) ) { media() Spacer(modifier = Modifier.size(Spacing.xlg)) header() Spacer(modifier = Modifier.size(Spacing.md)) content() Spacer(modifier = Modifier.size(Spacing.xlg)) buttons() }
  94. Private and con fi dential 94 Column( verticalArrangement = Arrangement.Center,

    horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.padding(Spacing.xlg) ) { media() Spacer(modifier = Modifier.size(Spacing.xlg)); header() Spacer(modifier = Modifier.size(Spacing.md)) content() Spacer(modifier = Modifier.size(Spacing.xlg)) buttons() }
  95. Private and con fi dential 95 Column( verticalArrangement = Arrangement.Center,

    horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.padding(Spacing.xlg) ) { media() Spacer(modifier = Modifier.size(Spacing.xlg)) ;; header() Spacer(modifier = Modifier.size(Spacing.md)) content() Spacer(modifier = Modifier.size(Spacing.xlg)) buttons() }
  96. Private and con fi dential 96 Column( verticalArrangement = Arrangement.Center,

    horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.padding(Spacing.xlg) ) { media() Spacer(modifier = Modifier.size(Spacing.xlg)); header() Spacer(modifier = Modifier.size(Spacing.md)) content() Spacer(modifier = Modifier.size(Spacing.xlg)) buttons() }
  97. Private and con fi dential 97 Column( verticalArrangement = Arrangement.Center,

    horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.padding(Spacing.xlg) ) { media() Spacer(modifier = Modifier.size(Spacing.xlg)) header() Spacer(modifier = Modifier.size(Spacing.md)) content() Spacer(modifier = Modifier.size(Spacing.xlg)) buttons() }
  98. Private and con fi dential 98 @Composable fun MatchScreen(config: MatchConfig)

    { CtaBox( media = { MatchPhotos( .. ) }, header = { Text(text="",style=TextStyles.H1) }, content = { Text(text="",style=TextStyles.P1) }, buttons = { Button( text = " ... ", color = Color.White, contentColor = Color.Black ) } ) }
  99. Private and con fi dential 99

  100. Private and con fi dential 1. Design syste m •

    Token s • Component s • Pattern s 2. Android View intero p 3. Testin g 4. Integration 100
  101. Android Views + Compose 101

  102. Private and con fi dential 102 val count = remember

    { mutableStateOf(0) } Column { Text(text = "Compose Text Count = ${count.value}") AndroidView( viewBlock = { context -> TextView(context).apply { setOnClickListener { count.value = count.value + 1 } } }, update = { view -> view.text = "AndroidViews World! Count = ${count.value}" } ) }
  103. Private and con fi dential 103 val count = remember

    { mutableStateOf(0) } Column { Text(text = "Compose Text Count = ${count.value}") AndroidView( viewBlock = { context -> TextView(context).apply { setOnClickListener { count.value = count.value + 1 } } }, update = { view -> view.text = "AndroidViews World! Count = ${count.value}" } ) }
  104. Private and con fi dential 104 val count = remember

    { mutableStateOf(0) } Column { Text(text = "Compose Text Count = ${count.value}") AndroidView( viewBlock = { context -> TextView(context).apply { setOnClickListener { count.value = count.value + 1 } } }, update = { view -> view.text = "AndroidViews World! Count = ${count.value}" } ) }
  105. Private and con fi dential 105 val count = remember

    { mutableStateOf(0) } Column { Text(text = “Compose Count = ${count.value}") AndroidView( viewBlock = { context -> TextView(context).apply { setOnClickListener { count.value = count.value + 1 } } }, update = { view -> view.text = " .. . Count = ${count.value}" } ) }
  106. Compose + Android Views 106

  107. Private and con fi dential <LinearLayout> <androidx.compose.ui.platform.ComposeView android:id="@+id/composeView" .../> <TextView

    android:id="@+id/androidView" .../> </ LinearLayout> 107
  108. Private and con fi dential override fun onCreate() { val

    composeView = findViewById<ComposeView>( .. ) composeView.setContent { Text(text = "ComposeText") } } 108
  109. Private and con fi dential override fun onCreate() { val

    composeView = findViewById<ComposeView>( .. ) composeView.setContent { Text(text = "ComposeText") } } 109
  110. Private and con fi dential 1. Design syste m •

    Token s • Component s • Pattern s 2. Android View intero p 3. Testin g 4. Integration 110
  111. Testing 111

  112. Private and con fi dential 112 object TestTagMatchScreen { const

    val button = "MatchScreenButton" ... } Button( ... modifier = Modifier.testTag(TestTagMatchScreen.button) )
  113. Private and con fi dential 113 @get:Rule val composeTestRule =

    createComposeRule() fun launchScreen() { composeTestRule.setContent { BadooTheme { MatchScreen() } } }
  114. Private and con fi dential 114 @Test fun matchScreenTest() {

    launchScreen() composeTestRule .onNodeWithTag(TestTagMatchScreen.button) .assertIsDisplayed() .assertTextEquals("Send Message") .performClick() } }
  115. Private and con fi dential 115 @Test fun interopTest() {

    launchScreen() onView(ViewMatchers.withId(R.dimen.counterAndroidView)) .perform(click()) .check(matches(withText("Hello From AndroidViews World! Count = 1"))) composeTestRule .onNodeWithText(“Compose Text Count = 1").assertExists() }
  116. Private and con fi dential 116 @Test fun interopTest() {

    launchScreen() onView(ViewMatchers.withId(R.dimen.counterAndroidView)) .perform(click()) .check(matches(withText("Hello From AndroidViews World! Count = 1"))) composeTestRule .onNodeWithText(“Compose Text Count = 1").assertExists() }
  117. Private and con fi dential 117 @Test fun interopTest() {

    launchScreen() onView(ViewMatchers.withId(R.id.counterAndroidView)) .perform(click()) .check(matches(withText("Hello From AndroidViews World! Count = 1"))) composeTestRule .onNodeWithText(“Compose Text Count = 1").assertExists() }
  118. Private and con fi dential Screenshot tests Karumi — Shot

    118
  119. Private and con fi dential 1. Design syste m •

    Token s • Component s • Pattern s 2. Android View intero p 3. Testin g 4. Integration 119
  120. Integration 120

  121. Private and con fi dential Business logic integration — MVI

    121 Feature Compose State UI events
  122. Private and con fi dential Business logic integration — MVVM

    122 ViewModel Compose LiveData Method calls
  123. Private and con fi dential val value: String by observable.subscribeAsState("initial")

    val value: String by liveData.observeAsState(“initial") val value: String? by observable.subscribeAsState(null) val value: String? by liveData.observeAsState(null) Text("Value is $value") 123
  124. Private and con fi dential Business logic integration — MVP

    124 Method Calls ViewWrapper Compose State UI events Presenter
  125. Private and con fi dential ✅ How to create a

    design syste m ✅ How to integrate with the current U I ✅ How to tes t ✅ How to integrate within the business logic Summary 125
  126. Private and con fi dential • Set of main UI-component

    s • Experiment s • Non critical feature s • Wait for stable releas e • Ship to prod 🚀🚀🚀 Next steps 126
  127. Private and con fi dential • Design system talk •

    Compose pathway • Сompose samples • Repo from the talk • Bumble Tech blog Resources 127
  128. 128 CHEERS ! antonshilov_