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

Composing an api in the right way

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Brian Wangi Brian Wangi
November 02, 2024
11

Composing an api in the right way

Avatar for Brian Wangi

Brian Wangi

November 02, 2024
Tweet

Transcript

  1. Constants const val DefaultKeyName = " defaultKey" val StructurallyEqual: ComparisonPolicy

    = StructurallyEqualsImpl() object ReferenceEqual : ComparisonPolicy {} sealed class LoadResult<T> { object Loading : LoadResult<Nothing>() class Done<T>(val result: T) : LoadResult<T>() class Error(val cause: Throwable) : LoadResult<Nothing>() }
  2. Constants const val DefaultKeyName = " defaultKey" val StructurallyEqual: ComparisonPolicy

    = StructurallyEqualsImpl() object ReferenceEqual : ComparisonPolicy {} sealed class LoadResult<T> { object Loading : LoadResult<Nothing>() class Done<T>(val result: T) : LoadResult<T>() class Error(val cause: Throwable) : LoadResult<Nothing>() } enum class Status { Idle, Busy, }
  3. Functions Return a value Emit content @Composable fun Button(...) @Composable

    fun Column(...) @Composable fun LaunchedEffect(...)
  4. Functions Return a value @Composable fun stringResource( @StringRes id: Int

    ): String @Composable fun rememberScrollState( initial: Int = 0 ): ScrollState Emit content @Composable fun Button(...) @Composable fun Column(...) @Composable fun LaunchedEffect(...)
  5. Functions @Composable fun ColorScheme(darkTheme: Boolean): ColorScheme @Composable fun colorScheme(darkTheme: Boolean):

    ColorScheme @Composable fun CoroutineScope(): CoroutineScope @Composable fun rememberCoroutineScope(): CoroutineScope
  6. Components @Composable fun Header() { Column { Text("Kotlin programming language")

    Text("Concise. Cross-platform. Fun.") Row { Button { Text("Docs") } Button { Text("Blog") } } } }
  7. Components @Composable fun Header() { Column { Text("Kotlin programming language")

    Text("Concise. Cross-platform. Fun.") Row { Button { Text("Docs") } Button { Text("Blog") } } } }
  8. Components @Composable fun Header() { Column { Text("Kotlin programming language")

    Text("Concise. Cross-platform. Fun.") Row { Button { Text("Docs") } Button { Text("Blog") } } } }
  9. Components Button { Text("Docs") } Button { Text("Blog") } @Composable

    fun Header() { Column { Row { } } } @Composable fun Title() { Text("Kotlin programming language") Text("Concise. Cross-platform. Fun.") }
  10. Components @Composable fun Header() { Column { Title() Row {

    Button { Text("Docs") } Button { Text("Blog") } } } } @Composable fun Title() { Text("Kotlin programming language") Text("Concise. Cross-platform. Fun.") }
  11. Components @Composable fun Header() { Column { Title() Row {

    Button { Text("Docs") } Button { Text("Blog") } } } } @Composable fun Title() { Text("Kotlin programming language") Text("Concise. Cross-platform. Fun.") }
  12. Components Koťli⭲ píogíammi⭲g la⭲g"agc Co⭲cisc. Cíoss-plaťroím. I"⭲. @Composable fun Title()

    { Column { Text("Kotlin programming language") Text("Concise. Cross-platform. Fun.") } }
  13. Components @Composable fun InputField(): UserInputState { // ... } val

    inputState = InputField() Button(onClick = { inputState.value = "" }) { Text("Clear input") }
  14. Components @Composable fun InputField(): UserInputState { // ... } Button(onClick

    = { inputState.value = "" }) { Text("Clear input") } val inputState = InputField()
  15. Components @Composable fun InputField(): UserInputState { // ... } val

    inputState = InputField() Button(onClick = { inputState.value = "" }) { Text("Clear input") }
  16. Components @Composable fun InputField(inputState: UserInputState) { // ... } val

    inputState = remember { UserInputState() } InputField ( inputState ) Button(onClick = { inputState.value = "" }) { Text("Clear input") }
  17. Components @Composable fun InputField(inputState: UserInputState) { // ... } val

    inputState = remember { UserInputState() } InputField(inputState) Button(onClick = { inputState.value = "" }) { Text("Clear input") }
  18. Components @Composable fun InputField(inputState: UserInputState) { // ... } val

    inputState = remember { UserInputState() } InputField(inputState) Button(onClick = { inputState.value = "" }) { Text("Clear input") }
  19. Components @Composable fun InputField(inputState: UserInputState) { // ... } val

    inputState = remember { UserInputState() } Button(onClick = { inputState.value = "" }) { Text("Clear input") } InputField(inputState)
  20. Default arguments @Composable fun Text( text: String, modifier: Modifier =

    Modifier, color: Color = Color.Unspecified, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current, )
  21. Default arguments @Composable fun Text( text: String, modifier: Modifier =

    Modifier, color: Color = Color.Unspecified, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current, )
  22. Default arguments @Composable fun Text( text: String, modifier: Modifier =

    Modifier, color: Color = Color.Unspecified, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current, )
  23. Default arguments @Composable fun Text( text: String, modifier: Modifier =

    Modifier, color: Color = Color.Unspecified, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle? = null, ) { val actualStyle = style ?: LocalTextStyle.current // ... }
  24. Default arguments @Composable fun Button( onClick: () -> Unit, modifier:

    Modifier = Modifier, enabled: Boolean = true, shape: Shape = ButtonDefaults.shape, colors: ButtonColors = ButtonDefaults.buttonColors(), elevation: ButtonElevation? = ButtonDefaults.buttonElevation(), border: BorderStroke? = null, contentPadding: PaddingValues = ButtonDefaults.ContentPadding, content: @Composable RowScope.() -> Unit, )
  25. Default arguments @Composable fun Button( onClick: () -> Unit, modifier:

    Modifier = Modifier, enabled: Boolean = true, shape: Shape = ButtonDefaults.shape, colors: ButtonColors = ButtonDefaults.buttonColors(), elevation: ButtonElevation? = ButtonDefaults.buttonElevation(), border: BorderStroke? = null, contentPadding: PaddingValues = ButtonDefaults.ContentPadding, content: @Composable RowScope.() -> Unit, )
  26. Default arguments object ButtonDefaults { val ContentPadding: PaddingValues val MinWidth:

    Dp val MinHeight: Dp val IconSize: Dp val IconSpacing: Dp val shape: Shape @Composable get @Composable fun buttonColors(...): ButtonColors = ButtonColors(...) @Composable fun buttonElevation(...): ButtonElevation = ButtonElevation(...) }
  27. Default arguments object ButtonDefaults { @Composable fun buttonColors(...): ButtonColors =

    ButtonColors(...) } Button( onClick = { viewModel.savePage() }, colors = customColors ?: ButtonDefaults.buttonColors(), ) { Text("Save") }
  28. Default arguments internal object ButtonDefaults { @Composable fun buttonColors(...): ButtonColors

    = ButtonColors(...) } Button( onClick = { viewModel.savePage() }, colors = customColors ?: ButtonDefaults.buttonColors(), ) { Text("Save") }
  29. Modifiers @Composable fun PageTitle( text: String, modifier: Modifier = Modifier,

    ) PageTitle( text = "Home", modifier = Modifier .width(100.dp) .height(40.dp) .background(Color.LightGray) .padding(horizontal = 12.dp) )
  30. Modifiers @Composable fun PageTitle( text: String, modifier: Modifier = Modifier,

    padding: Dp = 0.dp, ) PageTitle( text = "Home", modifier = Modifier .width(100.dp) .height(40.dp) .background(Color.LightGray) .padding(horizontal = 12.dp) )
  31. Modifiers @Composable fun PageTitle( text: String, ) Box( Modifier .padding(12.dp)

    .background(Color.Blue) PageTitle("Home") ) { } ) @Composable fun PageTitle( text: String, modifier: Modifier = Modifier, PageTitle( "Home", Modifier .padding(12.dp) .background(Color.Blue), )
  32. Modifiers › Accept a Modifier parameter › Named modifier ›

    First optional parameter › Applied to the root node
  33. Modifiers › Accept a Modifier parameter › Named modifier ›

    First optional parameter › Applied to the root node › Default value Modifier
  34. Modifiers › Accept a Modifier parameter › Named modifier ›

    First optional parameter › Applied to the root node › Default value Modifier
  35. Modifiers › Accept a Modifier parameter › Named modifier ›

    First optional parameter › Applied to the root node › Default value Modifier
  36. Modifiers › Accept a Modifier parameter › Named modifier ›

    First optional parameter › Applied to the root node › Default value Modifier
  37. Modifiers @Composable fun Header( modifier: Modifier = Modifier, ) {

    Column(modifier) { Title() Row { Button(onClick = { ... }) { Text("Docs") } Button(onClick = { ... }) { Text("Blog") } } } }
  38. Modifiers @Composable fun Header( modifier: Modifier = Modifier, ) {

    Column { Title() Row(modifier) { Button(onClick = { ... }) { Text("Docs") } Button(onClick = { ... }) { Text("Blog") } } } }
  39. Modifiers @Composable fun Header( modifier: Modifier = Modifier, ) {

    Column(modifier) { Title() Row { Button(onClick = { ... }) { Text("Docs") } Button(onClick = { ... }) { Text("Blog") } } } }
  40. Modifiers @Composable fun MenuItems( smallScreen: Boolean, modifier: Modifier = Modifier,

    ) { if (smallScreen) { Column(modifier) { Text(...) Text(...) } } else { Row(modifier) { Text(...) Text(...) } } }
  41. Modifiers › Accept a Modifier parameter › Named modifier ›

    First optional parameter › Applied to the root node › Default value Modifier
  42. Modifiers modifier: Modifier = Modifier interface Modifier { infix fun

    then(other: Modifier): Modifier = CombinedModifier(this, other) companion object : Modifier { ... override infix fun then(other: Modifier): Modifier = other override fun toString() = "Modifier" } }
  43. Modifiers @Composable fun Topics( topic: List<Topic>, modifier: Modifier = Modifier,

    ) { Row( modifier = modifier.padding(12.dp), ) { // ... } }
  44. Modifiers @Composable fun Topics( topic: List<Topic>, modifier: Modifier = Modifier,

    ) { Row( modifier = Modifier.padding(12.dp).then(modifier), ) { // ... } }
  45. Slots @Composable fun Button( content: @Composable () -> Unit, )

    Button { Row { Icon(Icons.Default.Build, "Build" ) Text("Build project" ) } }
  46. Slots Button { this: RowScope @Composable fun Button( content: @Composable

    RowScope.() -> Unit, ) { Row { content() } } Icon(Icons.Default.Build, "Build") Text("Build project" ) }
  47. Slots Button { this: RowScope @Composable fun Button( content: @Composable

    RowScope.() -> Unit, ) { Row { content() } } Icon(Icons.Default.Build, "Build") Text("Build project" ) }
  48. Slots Button { this: RowScope @Composable fun Button( content: @Composable

    RowScope.() -> Unit, ) { Row { content() } } Icon(Icons.Default.Build, "Build", Modifier.weight(1f)) Text("Build project", Modifier.weight(4f) ) }
  49. Slots @Composable fun Button( content: @Composable RowScope.() -> Unit, )

    { Row { content() } } Button { this: RowScope Icon(Icons.Default.Build, "Build", Modifier.weight(1f)) Text("Build project", Modifier.weight(4f)) }
  50. Slot lifecycle @Composable fun PreferenceItem( checked: Boolean, content: @Composable ()

    -> Unit, ) { if (checked) { Row { Text("Checked") content() } } else { Column { Text("Unchecked") content() } } }
  51. Slot lifecycle @Composable fun PreferenceItem( checked: Boolean, content: @Composable ()

    -> Unit, ) { if (checked) { Row { Text("Checked") content() } } else { Column { Text("Unchecked") content() } } }
  52. Slot lifecycle @Composable fun PreferenceItem( checked: Boolean, content: @Composable ()

    -> Unit, ) { if (checked) { Row { Text("Checked") content() } } else { Column { Text("Unchecked") content() } } } PreferenceItem
  53. Slot lifecycle @Composable fun PreferenceItem( checked: Boolean, content: @Composable ()

    -> Unit, ) { if (checked) { Row { Text("Checked") content() } } else { Column { Text("Unchecked") content() } } } PreferenceItem content
  54. Slot lifecycle @Composable fun PreferenceItem( checked: Boolean, content: @Composable ()

    -> Unit, ) { if (checked) { Row { Text("Checked") content() } } else { Column { Text("Unchecked") content() } } } checked = false checked = true checked = false PreferenceItem content
  55. Slot lifecycle @Composable fun PreferenceItem( checked: Boolean, content: @Composable ()

    -> Unit, ) { val movableContent = remember(content) { movableContentOf(content) } if (checked) { Row { Text("Checked") movableContent() } } else { Column { Text("Unchecked") movableContent()
  56. Slot lifecycle @Composable fun PreferenceItem( checked: Boolean, content: @Composable ()

    -> Unit, ) { val movableContent = remember(content) { movableContentOf(content) } if (checked) { Row { Text("Checked") movableContent() } } else { Column { Text("Unchecked") movableContent() jb.gg/movable-content-o f
  57. Ordering parameters › Required parameters › Optional parameters › First

    one: Modifier › Trailing content lambda @Composable fun NiaTopicTag( modifier: Modifier = Modifier, followed: Boolean, onClick: () -> Unit, enabled: Boolean = true, text: @Composable () -> Unit, )
  58. Ordering parameters › Required parameters › Optional parameters › First

    one: Modifier › Trailing content lambda @Composable fun NiaTopicTag( modifier: Modifier = Modifier, followed: Boolean, onClick: () -> Unit, enabled: Boolean = true, text: @Composable () -> Unit, )
  59. Ordering parameters NiaTopicTag(true, { toggleFollowed(id) }) { Text("Compose") } ›

    Required parameters › Optional parameters › First one: Modifier › Trailing content lambda ) @Composable fun NiaTopicTag( modifier: Modifier = Modifier, followed: Boolean, onClick: () -> Unit, enabled: Boolean = true, text: @Composable () -> Unit,
  60. Ordering parameters › Required parameters › Optional parameters › First

    one: Modifier › Trailing content lambda ) NiaTopicTag(true, { toggleFollowed(id) }) { Text("Compose") } @Composable fun NiaTopicTag( modifier: Modifier = Modifier, followed: Boolean, onClick: () -> Unit, enabled: Boolean = true, text: @Composable () -> Unit,
  61. Ordering parameters › Required parameters › Optional parameters › First

    one: Modifier › Trailing content lambda ) NiaTopicTag(true, { toggleFollowed(id) }) { Text("Compose") } @Composable fun NiaTopicTag( followed: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, text: @Composable () -> Unit,
  62. Ordering parameters › Required parameters › Optional parameters › First

    one: Modifier › Trailing content lambda ) NiaTopicTag(followed = true, onClick = { toggleFollowed(id) }) { Text("Compose") } @Composable fun NiaTopicTag( followed: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, text: @Composable () -> Unit,
  63. Ordering parameters › Required parameters › Optional parameters › First

    one: Modifier › Trailing content lambda ) NiaTopicTag(true, { toggleFollowed(id) }) { Text("Compose") } @Composable fun NiaTopicTag( followed: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, text: @Composable () -> Unit,
  64. Ordering parameters ) › Required parameters › Optional parameters ›

    First one: Modifier › Trailing content lambda NiaTopicTag(true, { toggleFollowed(id) }, Modifier.padding(8.dp)) { Text("Compose") } @Composable fun NiaTopicTag( followed: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, text: @Composable () -> Unit,
  65. Ordering parameters NiaTopicTag(true, { toggleFollowed(id) }, Modifier.padding(8.dp)) { Text("Compose") }

    ) › Required parameters › Optional parameters › First one: Modifier › Trailing content lambda @Composable fun NiaTopicTag( followed: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, text: @Composable () -> Unit,
  66. Ordering parameters NiaTopicTag(true, { toggleFollowed(id) }, Modifier.padding(8.dp)) { Text("Compose") }

    ) › Required parameters › Optional parameters › First one: Modifier › Trailing content lambda @Composable fun NiaTopicTag( followed: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, content: @Composable () -> Unit,
  67. @Composable fun Scroller( offset: State<Float>, onOffsetChange: (Float) -> Unit, )

    @Composable fun ContextMenu(offset: Float, onOffsetChange: (Float) -> Unit) { Row { MenuItems() Scroller(offset, onOffsetChange) } } State
  68. @Composable fun Scroller( offset: State<Float>, onOffsetChange: (Float) -> Unit, )

    @Composable fun ContextMenu(offset: Float, onOffsetChange: (Float) -> Unit) { val offsetState = remember { mutableStateOf(offset) } Row { MenuItems() Scroller(offsetState, onOffsetChange) } } State
  69. @Composable fun Scroller( offset: () -> Float, onOffsetChange: (Float) ->

    Unit, ) // Constant Scroller(offset = { 0f }, ...) // Plain value Scroller(offset = { offset }, ...) // State Scroller(offset = { offsetState.value }, ...) // Value from an object Scroller(offset = { someObject.offset }, ...) State
  70. State @Composable fun Scroller( offset: () -> Float, onOffsetChange: (Float)

    -> Unit, ) // Constant Scroller(offset = { 0f }, ...) // Plain value Scroller(offset = { offset }, ...) // State Scroller(offset = { offsetState.value }, ...) // Value from an object Scroller(offset = { someObject.offset }, ...) jb.gg/debugging-recompositio n
  71. State holders jb.gg/compose-stabilit y @Composable fun VerticalScroller( verticalScrollerState: VerticalScrollerState )

    @Stable interface VerticalScrollerState { var scrollPosition: Int var scrollRange: Int }
  72. State holders @Composable fun VerticalScroller( verticalScrollerState: VerticalScrollerState ) class VerticalScrollerStateImpl(

    scrollPosition: Int = 0, scrollRange: Int = 0, ) : VerticalScrollerState { override var scrollPosition: Int by mutableStateOf(scrollPosition) override var scrollRange: Int by mutableStateOf(scrollRange) }
  73. State holders @Composable fun VerticalScroller( verticalScrollerState: VerticalScrollerState ) fun VerticalScrollerState():

    VerticalScrollerState = VerticalScrollerStateImpl() private class VerticalScrollerStateImpl( scrollPosition: Int = 0, scrollRange: Int = 0, ) : VerticalScrollerState { override var scrollPosition: Int by mutableStateOf(scrollPosition) override var scrollRange: Int by mutableStateOf(scrollRange) }
  74. State holders @Composable fun VerticalScroller( verticalScrollerState: VerticalScrollerState = remember {

    VerticalScrollerState() } ) fun VerticalScrollerState(): VerticalScrollerState = VerticalScrollerStateImpl() private class VerticalScrollerStateImpl( scrollPosition: Int = 0, scrollRange: Int = 0, ) : VerticalScrollerState { override var scrollPosition: Int by mutableStateOf(scrollPosition) override var scrollRange: Int by mutableStateOf(scrollRange) }
  75. State holders @Composable fun VerticalScroller( verticalScrollerState: VerticalScrollerState = remember {

    VerticalScrollerState() } ) fun VerticalScrollerState(): VerticalScrollerState = VerticalScrollerStateImpl() @Stable interface VerticalScrollerState { var scrollPosition: Int var scrollRange: Int } private class VerticalScrollerStateImpl ( scrollPosition: Int = 0, scrollRange: Int = 0, ) : VerticalScrollerState { override var scrollPosition: Int by mutableStateOf(scrollPosition)
  76. State holders @Stable class VerticalScrollerState { var scrollPosition: Int by

    mutableStateOf(0) var scrollRange: Int by mutableStateOf(0) }
  77. State holders @Stable class VerticalScrollerState { var scrollPosition: Int by

    mutableStateOf(0) var scrollRange: Int by mutableStateOf(0) }
  78. Compose Multiplatform 🞄 jb.gg/compose Guidelines 🞄 jb.gg/compose-api-guidelines 🞄 jb.gg/compose-component-api- guidelines

    In order of appearance 🞄 jb.gg/composition-locals 🞄 jb.gg/compose-modifiers 🞄 jb.gg/movable-content-of 🞄 jb.gg/debugging-recomposition 🞄 jb.gg/compose-stability 🞄 jb.gg/compose-rules Composing an API the Right way Brian Wangi @dev_brian