Slide 1

Slide 1 text

Branching out to Jetpack Compose Chris Banes @chrisbanes Nacho López @mrmans0n !

Slide 2

Slide 2 text

!

Slide 3

Slide 3 text

Optional section title 3

Slide 4

Slide 4 text

Compose is on your device

Slide 5

Slide 5 text

...but how?

Slide 6

Slide 6 text

What we did was not perfect

Slide 7

Slide 7 text

What we didn't was not perfect Your journey will be different

Slide 8

Slide 8 text

8 Our journey

Slide 9

Slide 9 text

Twitter for Android

Slide 10

Slide 10 text

1000+ modules

Slide 11

Slide 11 text

300+ UI modules

Slide 12

Slide 12 text

30+ teams

Slide 13

Slide 13 text

Late 2020: How do we make UI dev faster?

Slide 14

Slide 14 text

How do we start using Compose? Late 2020:

Slide 15

Slide 15 text

16 We started with a Button Hi friends! Hi friends! Hi friends! Hi friends! Hi friends!

Slide 16

Slide 16 text

17 First, we had to sort out our theming ONE DOES NOT SIMPLY WRITE A COMPOSE COMPONENT

Slide 17

Slide 17 text

18 Design system Doesn't map 1:1 to Material

Slide 18

Slide 18 text

How do we implement our design system in Compose?

Slide 19

Slide 19 text

Lots of layers to build upon Compose is layered Runtime Fundamental parts of the Compose runtime UI Fundamental building blocks for the Compose UI Toolkit Foundation Design system agnostic components such as base layouts and animation Material Opinionated implementation of the Material Design system for Compose UI

Slide 20

Slide 20 text

Lots of layers to build upon Compose is layered Runtime Fundamental parts of the Compose runtime UI Fundamental building blocks for the Compose UI Toolkit Foundation Design system agnostic components such as base layouts and animation Material Opinionated implementation of the Material Design system for Compose UI Opinionated implementation of the Material Design system for Compose UI Material Compose

Slide 21

Slide 21 text

Runtime Fundamental parts of the Compose runtime UI Fundamental building blocks for the Compose UI Toolkit Foundation Design system agnostic components such as base layouts and animation Material Opinionated implementation of the Material Design system for Compose UI Compose Android framework Fundamental parts of the Compose runtime android.view.* Mostly fundamental UI layer. Contains some Material concepts and styling AppCompat + AndroidX Backports framework functionality Material Design Components Provides off-the-shelf components for Material Views Layers which contain Material

Slide 22

Slide 22 text

Most apps with their own design system will be somewhere in the middle Material 0

Slide 23

Slide 23 text

To maintain platform consistency Touch highlights App chrome Elevation Material 0

Slide 24

Slide 24 text

Material 0 Easier in Compose Build on Foundation Easier in Compose Everything is in one place and built as one Easier in views More difficult to use Compose components without using Material theming / concepts

Slide 25

Slide 25 text

You either: Big decision alert map your design system to Material or start afresh using Compose Foundation We're moving to here We started out here

Slide 26

Slide 26 text

2021 April May June July We had built out some of the core components and theming How do we actually use Compose across the app?

Slide 27

Slide 27 text

We found 4 teams who were eager to adopt Compose EAPs Started small to work with the teams as closely as possible Very informal Early access partners

Slide 28

Slide 28 text

Each team was soon to begin writing new UI EAPs Different requirements: lists, paging, text input, navigation Acted as a way to crowdsource our priority list Early access partners

Slide 29

Slide 29 text

We quickly found that supporting 4 teams concurrently was a lot of work EAPs We were providing in-depth feedback, and filling gaps Early access partners

Slide 30

Slide 30 text

Other teams then started using Compose, adding to the workload EAPs Those teams weren't always ready to support Compose Early access partners

Slide 31

Slide 31 text

Other non-EAP teams then started using Compose, adding to the workload EAPs Those teams weren't always ready to support Compose Early access partners

Slide 32

Slide 32 text

2021 mber December How do we spend less time on support?

Slide 33

Slide 33 text

List of modules which are 'allowed' to use Compose Allowlist Teams could request to be added to the allowlist Enabled us to find out what support was required before they started ...also allowed us to set expectations on what support we could provide

Slide 34

Slide 34 text

final def composeAllowedModules = project .fileContents("${project.rootDir}/path/to/allowlist.txt") .lines() final String modulePath = project.path final boolean moduleInComposeAllowlist = composeAllowedModules.anyMatch { it !" modulePath } if (!moduleInComposeAllowlist) { !# If the module is using Jetpack Compose but isn't in the allowlist!!$! tasks.withType(KotlinCompile).configureEach { task !% task.doFirst { !# We check to see if the Compose Compiler plugin has !# been added to the compiler args. final boolean usingComposeCompiler = (task as KotlinCompile) .kotlinOptions .freeCompilerArgs .any { it.contains('androidx.compose.compiler') } Allowlist Shepherds / / bit.ly/compose-allowlist

Slide 35

Slide 35 text

.fileContents("${project.rootDir}/path/to/allowlist.txt") .lines() final String modulePath = project.path final boolean moduleInComposeAllowlist = composeAllowedModules.anyMatch { it !" modulePath } if (!moduleInComposeAllowlist) { !# If the module is using Jetpack Compose but isn't in the allowlist!!$! tasks.withType(KotlinCompile).configureEach { task !% task.doFirst { !# We check to see if the Compose Compiler plugin has !# been added to the compiler args. final boolean usingComposeCompiler = (task as KotlinCompile) .kotlinOptions .freeCompilerArgs .any { it.contains('androidx.compose.compiler') } if (usingComposeCompiler) { throw new GradleException(!!$) } } } } Allowlist bit.ly/compose-allowlist

Slide 36

Slide 36 text

Allowlist Request form Are you available to spend the time learning Compose? Please state if you and your team will have enough time to onboard to Compose.

Slide 37

Slide 37 text

Allowlist Request form Are you available to spend the time learning Compose? Please state if you and your team will have enough time to onboard to Compose. Do you have the time to iterate on Client UI's feedback? Please let us know if you have urgent time concerns to ship your feature.

Slide 38

Slide 38 text

Allowlist Request form Are you available to spend the time learning Compose? Please state if you and your team will have enough time to onboard to Compose. Do you have the time to iterate on Client UI's feedback? Please let us know if you have urgent time concerns to ship your feature. What features from Compose do you need? Describe the features you anticipate you might need. If migrating from views, do you have metrics to make sure nothing regresses?

Slide 39

Slide 39 text

Allowlist Request form Are you available to spend the time learning Compose? Please state if you and your team will have enough time to onboard to Compose. Do you have the time to iterate on Client UI's feedback? Please let us know if you have urgent time concerns to ship your feature. What features from Compose do you need? Describe the features you anticipate you might need. If migrating from views, do you have metrics to make sure nothing regresses? Is your whole team on board? Let us know whether your team is onboard and willing to do code reviews for this feature.

Slide 40

Slide 40 text

Allowlist Request form Are you available to spend the time learning Compose? Please state if you and your team will have enough time to onboard to Compose. Do you have the time to iterate on Client UI's feedback? Please let us know if you have urgent time concerns to ship your feature. What features from Compose do you need? Describe the features you anticipate you might need. If migrating from views, do you have metrics to make sure nothing regresses? Is your whole team on board? Let us know whether your team is onboard and willing to do code reviews for this feature.

Slide 41

Slide 41 text

2022 June July August Se

Slide 42

Slide 42 text

Allowlist Any team can use Compose

Slide 43

Slide 43 text

A group of engineers there to 'shepherd' Compose adoption Automatically added to all Compose code reviews Keep an eye on Compose usage and trends W ork-in-progress Shepherds

Slide 44

Slide 44 text

A group of engineers there to 'shepherd' Compose adoption Goal: Keep expanding shepherds group from across the company Currently comprised from 5 teams Automatically added to all Compose code reviews W ork-in-progress Shepherds

Slide 45

Slide 45 text

2022 October

Slide 46

Slide 46 text

90+ Compose modules

Slide 47

Slide 47 text

20+ Teams using Compose

Slide 48

Slide 48 text

Using Compose at "

Slide 49

Slide 49 text

Twitter for Android First commit in the repo from 2010 A lot of custom infra built in Infra usually created before OSS alternatives

Slide 50

Slide 50 text

Examples of custom infra UI

Slide 51

Slide 51 text

UI DI Examples of custom infra

Slide 52

Slide 52 text

All our custom infra is properly val viewModel = weaverViewModel() val state by viewModel.watchAsState() supported

Slide 53

Slide 53 text

val viewModel = weaverViewModel() val state by viewModel.watchAsState() val myObject = viewSubgraph().myObject All our custom infra is properly supported

Slide 54

Slide 54 text

+ All our custom infra is properly documented

Slide 55

Slide 55 text

+ ...and so on All our custom infra is properly documented

Slide 56

Slide 56 text

72 documented properly

Slide 57

Slide 57 text

73

Slide 58

Slide 58 text

73

Slide 59

Slide 59 text

74 Documentation for all levels

Slide 60

Slide 60 text

75 Codelabs are great starter docs

Slide 61

Slide 61 text

76 Codelabs

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

Gotchas should be documented too

Slide 64

Slide 64 text

Gotchas Using custom emoji is not possible yet Interop performance when in a RecyclerView Complex accessibility is limited And others... it's a live document!

Slide 65

Slide 65 text

Best practices

Slide 66

Slide 66 text

Tools, not rules Best practices?

Slide 67

Slide 67 text

Tools, not rules Tooling We'll get there, but

Slide 68

Slide 68 text

Tools P re v ie w s Infra &

Slide 69

Slide 69 text

Previews

Slide 70

Slide 70 text

87 Previews multiple themes from Dolphin+

Slide 71

Slide 71 text

88 Standard Lights out Dim 3 themes need previews Previews

Slide 72

Slide 72 text

89 enum class ThemeVariant(private val themeSuffix: String) { } STANDARD(".Standard"), DIM(".Dim"), LIGHTS_OUT(".LightsOut"); !# !!$

Slide 73

Slide 73 text

90 class ThemeVariantPreviewProvider : PreviewParameterProvider { override val values = ThemeVariant.values().asSequence() } enum class ThemeVariant(private val themeSuffix: String) { } !!$

Slide 74

Slide 74 text

91 @Preview @Composable fun MyPreview( ) { HorizonTheme(themeVariant = variant) { MyComposable() } } @PreviewParameter(ThemeVariantPreviewProvider!&class) variant: ThemeVariant class ThemeVariantPreviewProvider : PreviewParameterProvider { override val values = ThemeVariant.values().asSequence() }

Slide 75

Slide 75 text

91 @Preview @Composable fun MyPreview( ) { HorizonTheme(themeVariant = variant) { MyComposable() } } @PreviewParameter(ThemeVariantPreviewProvider!&class) variant: ThemeVariant class ThemeVariantPreviewProvider : PreviewParameterProvider { override val values = ThemeVariant.values().asSequence() }

Slide 76

Slide 76 text

@Preview @Composable fun MyPreview( ) { HorizonTheme(themeVariant = variant) { MyComposable() } } @PreviewParameter(ThemeVariantPreviewProvider!&class) variant: ThemeVariant We use our own CompositePreviewProvider @PreviewParameter( ) @PreviewParameter( ) !&class https://bit.ly/composite-preview-provider

Slide 77

Slide 77 text

93 @Preview @Composable fun MyPreview( ) { val (variant, myData) = model HorizonTheme(themeVariant = variant) { MyComposable(myData) } } @PreviewParameter( ) !&class MyPreviewProvider model: Pair val (variant, myData) = model themeVariant = variant myData https://bit.ly/composite-preview-provider

Slide 78

Slide 78 text

94 Previews custom infra run in their own A that we don't c

Slide 79

Slide 79 text

95 Previews run in their own Activity that we don't control

Slide 80

Slide 80 text

97 val viewModel = weaverViewModel() val subgraph = viewSubgraph() val starter = rememberContentViewStarter() starter.onResult { result !% !!$ } val dialogController = rememberDialogController() when running on Preview Do not crash

Slide 81

Slide 81 text

98 Do not crash

Slide 82

Slide 82 text

98 Do not crash

Slide 83

Slide 83 text

99 LocalInspectionM

Slide 84

Slide 84 text

100 LocalInspectionMode .current = true // when in a preview

Slide 85

Slide 85 text

101 LocalInspectionMode Provide defaults if set ent = true // when in a preview

Slide 86

Slide 86 text

102 internal object HorizonFontFamily { val default: FontFamily @Composable @ReadOnlyComposable get() = when { LocalInspectionMode.current !% chirpFontFamily FontFeatures.isChirpEnabled() !% chirpFontFamily else !% FontFamily.Default } private val chirpFontFamily by lazy { !!$ } } Provide defaults if set A sensible default

Slide 87

Slide 87 text

103 internal object HorizonFontFamily { val default: FontFamily @Composable @ReadOnlyComposable get() = when { LocalInspectionMode.current !% chirpFontFamily FontFeatures.isChirpEnabled() !% chirpFontFamily else !% FontFamily.Default } private val chirpFontFamily by lazy { !!$ } } A sensible default

Slide 88

Slide 88 text

104 internal object HorizonFontFamily { val default: FontFamily @Composable @ReadOnlyComposable get() = when { LocalInspectionMode.current !% chirpFontFamily FontFeatures.isChirpEnabled() !% chirpFontFamily else !% FontFamily.Default } private val chirpFontFamily by lazy { !!$ } } A sensible default

Slide 89

Slide 89 text

LiveEdit are pretty picky I want to believe

Slide 90

Slide 90 text

106 Feature Parity with Views is no

Slide 91

Slide 91 text

107 Feature Parity h Views is not quite there yet

Slide 92

Slide 92 text

108 Feature Parity systems quite there yet AndroidView is a life saver!

Slide 93

Slide 93 text

109 Gotchas

Slide 94

Slide 94 text

@Composable @UiComposable fun AndroidView( factory: (Context) !% T, modifier: Modifier = Modifier, update: (T) !% Unit = NoOpUpdate ) { val context = LocalContext.current !# NoOp Connection required by nested scroll modifier. Th !# to influence nested scrolling with it and it is requir 110 AndroidView are pretty picky use Views when necessary

Slide 95

Slide 95 text

111 Text for custom emoji TextField for media embeds and custom emoji weetView interop with our modular view collection

Slide 96

Slide 96 text

112 Text

Slide 97

Slide 97 text

113 Created a new composable as a Text facade Which one do we use? Compose or View? Decide via heuristics Allow forcing the wrapper Our facade share a similar API with Text Use the original as fallback Text https://bit.ly/compose-textview-wrapper

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

4:55PM · May 13, 2021 6 16 46 Declarative programming (including Compose) usually takes 3-6 months of usage before people have the ""⚡holly-shit this is good" moment. @JimSproch Jim Sproch AndroidView is a life saver! Until then, you will struggle with it, you will fight it, it will frustrate you, and then it will all click in your mind six months later.

Slide 100

Slide 100 text

4:55PM · May 13, 2021 6 16 46 Declarative programming (including Compose) usually takes 3-6 months of usage before people have the ""⚡holly-shit this is good" moment. @JimSproch Jim Sproch AndroidView is a life saver! Until then, you will struggle with it, you will fight it, it will frustrate you, and then it will all click in your mind six months later.

Slide 101

Slide 101 text

Not everybody starts at the same page Onboarding to Compose

Slide 102

Slide 102 text

Not everybody starts at the same page Onboarding to Compose Easy to make sneaky mistakes

Slide 103

Slide 103 text

15h of review time per week per engineer Nth time you repeat the same feedback for a different person

Slide 104

Slide 104 text

Nth time you repeat the same feedback for a different person h er er

Slide 105

Slide 105 text

Static checks Best ROI for our team Scale really well Bikeshedding is time consuming and frustrating for all parts involved Quality error messages that link to the documentation

Slide 106

Slide 106 text

Static checks

Slide 107

Slide 107 text

Static checks Allows custom rules and has autofixes Lint has been breaking a lot with AGP updates over the years Really fast, runs in all our pre-commit git hooks Great IDE plugin (unofficial)

Slide 108

Slide 108 text

Static checks 5:15PM · Mar 25, 2022 19 164 638 A big challenge to face when a big team with a large codebase starts adopting Compose is that not everybody will start at the same page. This happened to as at Twitter. @mrmans0n Nacho López $ Compose is %, allows for amazing things but has a bunch of footguns to be aware of.

Slide 109

Slide 109 text

Static checks 5:15PM · Mar 25, 2022 19 164 638 A big challenge to face when a big team with a large codebase starts adopting Compose is that not everybody will start at the same page. This happened to as at Twitter. @mrmans0n Nacho López $ Compose is %, allows for amazing things but has a bunch of footguns to be aware of. Open source pretty please? Asking for a friend... 7:35PM · Mar 25, 2022 Harold @hidethepain

Slide 110

Slide 110 text

Static checks 5:15PM · Mar 25, 2022 19 164 638 A big challenge to face when a big team with a large codebase starts adopting Compose is that not everybody will start at the same page. This happened to as at Twitter. @mrmans0n Nacho López $ Compose is %, allows for amazing things but has a bunch of footguns to be aware of. Open source pretty please? Asking for a friend... 7:35PM · Mar 25, 2022 Harold @hidethepain Y U NO DETEKT?!?1?1!?! 6:53PM · Mar 25, 2022 Android 4 lyf @hardcore_engineer

Slide 111

Slide 111 text

Static checks Compose Rules https://github.com/twitter/compose-rules Detekt

Slide 112

Slide 112 text

Static checks Compose Rules https://github.com/twitter/compose-rules Detekt

Slide 113

Slide 113 text

Hoist all the things

Slide 114

Slide 114 text

@Composable fun MyComposable(viewModel: MyViewModel = viewModel()) { MyOtherComposable(viewModel) } @Composable fun MyOtherComposable(viewModel: MyViewModel) { !!$ } Hoist all the things

Slide 115

Slide 115 text

@Composable fun MyComposable(viewModel: MyViewModel = viewModel()) { MyOtherComposable(viewModel) } @Composable fun MyOtherComposable(viewModel: MyViewModel) { !!$ } Hoist all the things

Slide 116

Slide 116 text

ViewModels should not be passed around: hoist state and send events back via lambdas @Composable fun MyComposable(viewModel: MyViewModel = viewModel()) { MyOtherComposable(viewModel) } @Composable fun MyOtherComposable(viewModel: MyViewModel) { !!$ } @Composable fun MyComposable(viewModel: MyViewModel = viewModel()) { val state by viewModel.state.collectAsState() MyOtherComposable(state) { value !% viewModel.someEvent(value) } } Hoist all the things

Slide 117

Slide 117 text

@Composable fun MyComposable(list: ArrayList) { !!$ } @Composable fun MyComposable(state: MutableState) { !!$ } Don't use mutable types as params in a Composable Hoist all the things

Slide 118

Slide 118 text

Avoid implicit dependencies

Slide 119

Slide 119 text

@Composable fun MyComposable() { val viewModel by viewModel() !# !!$ } Avoid implicit dependencies

Slide 120

Slide 120 text

Inject your ViewModels as default parameters @Composable fun MyComposable( viewModel: MyViewModel = viewModel() ) { !# !!$ } @Composable fun MyComposable() { val viewModel by viewModel() !# !!$ } Avoid implicit dependencies

Slide 121

Slide 121 text

Inject your DI dependencies as default parameters @Composable fun MyComposable( ) { !# !!$ } @Composable fun MyComposable() { !# !!$ } ers val myObject = viewSubgraph.myObject myObject: MyObject = viewSubgraph.myObject Avoid implicit dependencies

Slide 122

Slide 122 text

val myObject = viewSubgraph.myObject myObject: MyObject = viewSubgraph.myObject Implicit dependencies should be made explicit in the composable method signature @Composable fun MyComposable( ) { !# !!$ } @Composable fun MyComposable() { !# !!$ } val myObject = anyDependency() myObject: MyObject = anyDependency() ters Avoid implicit dependencies

Slide 123

Slide 123 text

val LocalBanana = staticCompositionLocalOf { & } val LocalApple = compositionLocalOf { ' } New CompositionLocals should be avoided * unless they are necessary for app-wide infra Avoid implicit dependencies

Slide 124

Slide 124 text

( needless recompositions

Slide 125

Slide 125 text

@Composable fun UsersList(users: List) { LazyColumn { items(users) { user !% User(user) } } } ( needless recompositions

Slide 126

Slide 126 text

( needless recompositions @Composable fun UsersList(users: List) { LazyColumn { items(users) { user !% User(user) } } }

Slide 127

Slide 127 text

@Composable fun UsersList(users: List) { LazyColumn { items(users) { user !% User(user) } } } Use Kotlinx Immutable Collections * or Immutable wrappers ( needless recompositions @Composable fun UsersList(users: ImmutableList) { !!$ } (users: UserList) { !!$ } !' or… !( @Immutable data class UserList(val items: List)

Slide 128

Slide 128 text

@Composable fun MyComposable(viewModel: MyViewModel = viewModel()) { OtherComposable(!!$) { viewModel.doSomething(it) } } ( needless recompositions

Slide 129

Slide 129 text

@Composable fun MyComposable(viewModel: MyViewModel = viewModel()) { OtherComposable(!!$) { viewModel.doSomething(it) } } ( needless recompositions @Composable fun MyComposable(viewModel: MyViewModel = viewModel()) { OtherComposable(!!$, viewModel!&doSomething) } If possible, use method references instead of capturing lambdas https://multithreaded.stitchfix.com/blog/2022/08/05/jetpack-compose-recomposition/

Slide 130

Slide 130 text

Modifiers for the win

Slide 131

Slide 131 text

@Composable fun MyComposable() { Column { Text("Hi~") } }

Slide 132

Slide 132 text

@Composable fun MyComposable() { Column { Text("Hi~") } } @Composable fun MyComposable(modifier: Modifier = Modifier) { Column(modifier = modifier) { Text("Hi~") } } Always provide a modifier https://chris.banes.dev/always-provide-a-modifier/

Slide 133

Slide 133 text

@Composable fun MyComposable(modifier: Modifier = Modifier) { Column(modifier = modifier) { Text( text = "Hi~", modifier = modifier ) } }

Slide 134

Slide 134 text

@Composable fun MyComposable(modifier: Modifier = Modifier) { Column(modifier = modifier) { Text( text = "Hi~", modifier = modifier ) } } Do not reuse modifiers

Slide 135

Slide 135 text

Static checks We wrote static checks, based on common issues we saw in code reviews We expanded the error messages for the rules and added links to the docs We created our best practices docs based on these custom rules Open source ) tools, not rules There are a couple internal rules still in our codebase, mostly around our internal MVI, DI and design systems use cases github.com/twitter/compose-rules

Slide 136

Slide 136 text

Testing

Slide 137

Slide 137 text

Testing We heavily use Writing comprehensive UI tests is tricky for us Almost all of our tests are JVM tests And tests? Very little device tests, managed by our Quality Engineering team

Slide 138

Slide 138 text

Testing Still Screenshot tests for peace of mind For AndroidViews we use on top Paparazzi Still JVM tests

Slide 139

Slide 139 text

Paparazzi https://github.com/cashapp/paparazzi

Slide 140

Slide 140 text

Paparazzi Doesn't like so we remove it from Paparazzi-enabled modules We auto-clip transparency from renders to save space and make code reviews easier We also add borders around components Our Paparazzi composable runs with LocalInspectionMode set to true https://bit.ly/paparazzi-twitter

Slide 141

Slide 141 text

Using Compose at ! Docs available for all levels Optimize @Previews working reliably AndroidView to get feature parity Static checks to scale adoption Unit + screenshot test composables

Slide 142

Slide 142 text

155 Benefits, gotchas & mistakes

Slide 143

Slide 143 text

Benefits such fast much simpler API wow stateless very performance so testing much kotlin

Slide 144

Slide 144 text

Benefits Working closer with UX Compose gave us a chance to reset our working relationship with UX and Product teams We can now iterate on component feedback much faster Creating the components has allowed us to influence the design system

Slide 145

Slide 145 text

Gotchas Compose is moving fast Takes effort to keep everyone up to date Lots of experimental APIs, which are likely to change The separate layers and libraries can be daunting

Slide 146

Slide 146 text

Gotchas Tied to Kotlin version Compose Compiler is tied to the specific version of Kotlin it was built against Recently stuck on an old version Compose due to being pinned to an old Kotlin version Due to various Kotlin / kapt issues

Slide 147

Slide 147 text

Gotchas Tied to Kotlin version Compose Compiler is tied to the specific version of Kotlin it was built against Recently stuck on an old version Compose due to being pinned to an old Kotlin version You can pin just the Compose Compiler version, and upgrade Compose Runtime → Material as needed

Slide 148

Slide 148 text

Mistakes Creating wrappers for small components It's very easy to create View wrappers for composables using ComposeView We've found that using many ComposeViews tends to scale badly in terms of performance Very easy to include multiple ComposeView instances in performance critical UIs (such as list items)

Slide 149

Slide 149 text

Mistakes Creating wrappers for small components Be purposeful when choosing what composables to wrap Prefer wrapping medium → large pieces of UI, to negate the ‘cost’ of ComposeView For example, don't wrap your Button() composable like we did

Slide 150

Slide 150 text

Thank You!