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

Designing scalable Compose APIs

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Designing scalable Compose APIs

Avatar for Yves Kalume

Yves Kalume

May 25, 2024
Tweet

More Decks by Yves Kalume

Other Decks in Programming

Transcript

  1. API A set of operations a software component (i.e., a

    system, a sub-system, class, or a function) provides to its clients
  2. What I mean by Scalable Which can be used in

    a large project by a big team of developers.
  3. How to check the quality of an API • Comprehensibility

    • Consistency • Discoverability • Simple Tasks Should Be Easy • Preservation of Investment
  4. @Composable fun Button( onClick: () -> Unit = {}, checked:

    Boolean = false, onCheckedChange: (Boolean) -> Unit, ) { --. } Don’t do this
  5. @Composable fun Button( onClick: () -> Unit, ) { --.

    } @Composable fun ToggleButton( checked: Boolean, onCheckedChange: (Boolean) -> Unit, ) { --. }
  6. Component or Modifier • Make a component if it has

    a distinct UI that cannot be applied to other components • wants to make structural changes in the UI (add/remove other components). • Modifier instead if the bit of functionality can be applied to any arbitrary single component
  7. @Composable fun Padding(allSides: Dp) { -/ impl } -/ Usage

    Padding(12.dp) { UserCard() UserPicture() } Don’t do this
  8. @Composable fun AnimatedVisibility( visibile: Boolean, modifier: Modifier = Modifier, content:

    @Composable () -> Unit ) { -/ impl } AnimatedVisibility(visible = false) { UserCard() }
  9. Component layering • Low level Components : small highly customizable

    components • High Level Components : a component made with multiple small components
  10. @Composable fun Checkbox(--.) { --. } @Composable fun Text(--.) {

    --. } @Composable fun Row(--.) { --. } Low level components
  11. fun rememberCoroutineScope( -/--. ): CoroutineScope { val wrapper = remember

    { -/Creating the scope } return wrapper.coroutineScope } Composables that internally remember {}s and returns a mutable object have the prefix remember.
  12. Component parameters • Do not introduce optional parameters that add

    optional behavior that could be added via Modifier
  13. @Composable fun Image( bitmap: ImageBitmap, onClick: () -> Unit =

    {}, modifier: Modifier = Modifier, clipToCircle: Boolean = false ) Don’t do this
  14. @Composable fun Button( onClick: () -> Unit, modifier: Modifier =

    Modifier, backgroundColor: Color = MaterialTheme.colors.primary )
  15. Modifier parameter • Has the Type Modifier • Is the

    first optional parameter • Has a no-op default value Modifier • Is the only parameter of type Modifier in the parameter list • Is applied once • as a first modifier in the chain to the root-most layout
  16. @Composable fun IconButton( buttonBitmap: ImageBitmap, modifier: Modifier = Modifier.defaultMinSize(48.dp), tint:

    Color = Color.Black ) { Box(Modifier.padding(16.dp)) { Icon( buttonBitmap, modifier = Modifier.aspectRatio(1f).then(modifier), tint = tint ) } } Don’t do this
  17. @Composable fun IconButton( buttonBitmap: ImageBitmap, modifier: Modifier = Modifier, tint:

    Color = Color.Black ) { Box(modifier.defaultMinSize(48.dp).padding(16.dp)) { Icon( buttonBitmap, modifier = Modifier.aspectRatio(1f), tint = tint ) } }
  18. Parameters order 1. Required parameters. 2. Single modifier: Modifier =

    Modifier. 3. Optional parameters. 4. (optional) trailing @Composable lambda.
  19. Prefer Stateless components • Use state Hoisting • Don’t pass

    MutableState<T> or State<T> as a parameter • Instead of State<T> use the type (ex: Int) • Or param: () -> Float. To delay reading the value.
  20. @Composable fun Checkbox( isChecked: Boolean, onToggle: () -> Unit )

    { -/--. } Checkbox( myState.optIn, onToggle = { myState.optIn = !myState.optIn } )
  21. @Composable fun Button( onClick: () -> Unit, enabled: Boolean =

    true, backgroundColor = if (enabled) BtnColor else DisabledColor, elevation = if (enabled) -/--. content: @Composable RowScope.() -> Unit ) {}
  22. class ButtonColors( backgroundColor: Color, disabledBackgroundColor: Color, contentColor: Color, disabledContentColor: Color

    ) { fun backgroundColor(enabled: Boolean): Color { --. } fun contentColor(enabled: Boolean): Color { --. } } Isolate this to a domain specific class.
  23. object ButtonDefaults { fun colors( backgroundColor: Color = --., disabledBackgroundColor:

    Color = --., contentColor: Color = --., disabledContentColor: Color = --. ): ButtonColors { --. } } You can even create a factory with default values
  24. @Composable fun Button( onClick: () -> Unit, enabled: Boolean =

    true, colors: ButtonColors = ButtonDefaults.colors(), content: @Composable RowScope.() -> Unit ) { val backgroundColor = colors.backgroundColor(enabled) }
  25. Documenting a component • One-liner paragraph summarizing the component and

    what it does. • @sample tag providing an example of the usage for this components and its states, default, etc • @see tags pointing to other related apis. • Links to design or other materials to help to use the components to its full potential. • Description for each parameter of the component, starting with @param
  26. Evolution of the Component APIs • Parameters of the functions

    MUST NOT be removed. • Newly added parameter to existing functions MUST have default expressions. • New parameters may be added as a last parameter, or second to last in cases of trailing lambdas.
  27. References • Designing scalable Compose APIs (Google IO 2024) •

    [Compose API Guidelines] https://github.com/androidx/androidx • [Kotlin API Guidelines] https://github.com/Kotlin/api-guidelines