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

Composing an API the *right* way (Droidcon Italy 2023)

Composing an API the *right* way (Droidcon Italy 2023)

Everyone who writes code using Jetpack Compose designs Composable functions and components all the time. In this talk, we’ll take a look at some highlights from the official guidelines around designing Compose APIs, to see how we can do a better job of building with Compose.

Marton Braun

October 13, 2023
Tweet

More Decks by Marton Braun

Other Decks in Programming

Transcript

  1. @zsmb13
    Composing an API
    the way
    Márton Braun
    Developer Advocate, JetBrains

    View full-size slide

  2. API design
    Framework
    Library
    App

    View full-size slide

  3. API design
    Framework
    Library
    App

    View full-size slide

  4. Guidelines
    jb.gg/compose

    View full-size slide

  5. Guidelines
    jb.gg/compose

    View full-size slide

  6. Agenda
    › Naming
    › Parameters
    › Default values
    › Modifiers
    › Slots
    › State handling

    View full-size slide

  7. Constants
    const val SOME_LOUD_CONSTANT = 999999

    View full-size slide

  8. Constants
    const val SOME_LOUD_CONSTANT = 999999

    View full-size slide

  9. Constants
    const val DefaultKeyName = "__defaultKey"

    View full-size slide

  10. Constants
    const val DefaultKeyName = "__defaultKey"
    val StructurallyEqual: ComparisonPolicy = StructurallyEqualsImpl()

    View full-size slide

  11. Constants
    const val DefaultKeyName = "__defaultKey"
    val StructurallyEqual: ComparisonPolicy = StructurallyEqualsImpl()
    object ReferenceEqual : ComparisonPolicy {}
    sealed class LoadResult {
    object Loading : LoadResult()
    class Done(val result: T) : LoadResult()
    class Error(val cause: Throwable) : LoadResult()
    }

    View full-size slide

  12. Constants
    const val DefaultKeyName = "__defaultKey"
    val StructurallyEqual: ComparisonPolicy = StructurallyEqualsImpl()
    object ReferenceEqual : ComparisonPolicy {}
    sealed class LoadResult {
    object Loading : LoadResult()
    class Done(val result: T) : LoadResult()
    class Error(val cause: Throwable) : LoadResult()
    }
    enum class Status {
    Idle,
    Busy,
    }

    View full-size slide

  13. Functions
    It depends.

    View full-size slide

  14. Functions
    Return a value
    Emit content

    View full-size slide

  15. Functions
    Return a value
    Emit content

    View full-size slide

  16. Functions
    Return a value
    Emit content

    View full-size slide

  17. Functions
    Return a value
    Emit content
    @Composable
    fun Column(...)
    @Composable
    fun LaunchedEffect(...)
    @Composable
    fun Button(...)

    View full-size slide

  18. Functions
    Return a value
    @Composable
    fun stringResource(
    @StringRes id: Int
    ): String
    @Composable
    fun rememberScrollState(
    initial: Int = 0
    ): ScrollState
    Emit content
    @Composable
    fun Column(...)
    @Composable
    fun LaunchedEffect(...)
    @Composable
    fun Button(...)

    View full-size slide

  19. Functions
    @Composable
    fun ColorScheme(darkTheme: Boolean): ColorScheme

    View full-size slide

  20. Functions
    @Composable
    fun ColorScheme(darkTheme: Boolean): ColorScheme

    View full-size slide

  21. Functions
    @Composable
    fun ColorScheme(darkTheme: Boolean): ColorScheme
    @Composable
    fun colorScheme(darkTheme: Boolean): ColorScheme

    View full-size slide

  22. Functions
    @Composable
    fun ColorScheme(darkTheme: Boolean): ColorScheme
    @Composable
    fun colorScheme(darkTheme: Boolean): ColorScheme
    @Composable
    fun rememberCoroutineScope(): CoroutineScope
    @Composable
    fun CoroutineScope(): CoroutineScope

    View full-size slide

  23. Component
    /kəmˈpəʊnənt/ noun

    View full-size slide

  24. Component
    /kəmˈpəʊnənt/ noun
    a @Composable function that returns Unit and
    emits exactly one Compose UI tree node.

    View full-size slide

  25. Component
    /kəmˈpəʊnənt/ noun
    a @Composable function that returns Unit and
    emits exactly one Compose UI tree node.

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  35. Component
    /kəmˈpəʊnənt/ noun
    a @Composable function that returns Unit and
    emits exactly one Compose UI tree node.

    View full-size slide

  36. Component
    /kəmˈpəʊnənt/ noun
    a @Composable function that returns Unit and
    emits exactly one Compose UI tree node.

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  44. Components
    Data UI

    View full-size slide

  45. CompositionLocal
    jb.gg/composition-locals

    View full-size slide

  46. CompositionLocal
    jb.gg/composition-locals

    View full-size slide

  47. CompositionLocal
    jb.gg/composition-locals

    View full-size slide

  48. CompositionLocal
    jb.gg/composition-locals

    View full-size slide

  49. CompositionLocal
    Data
    Styles
    jb.gg/composition-locals

    View full-size slide

  50. >
    Parameters CompositionLocal
    explicit implicit

    View full-size slide

  51. 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,
    )

    View full-size slide

  52. 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,
    )

    View full-size slide

  53. @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,
    Default arguments
    )

    View full-size slide

  54. 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
    // ...
    }
    ) {

    View full-size slide

  55. 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,
    )

    View full-size slide

  56. 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,
    )

    View full-size slide

  57. 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 buttonElevation(...): ButtonElevation = ButtonElevation(...)
    }
    @Composable
    fun buttonColors(...): ButtonColors = ButtonColors(...)
    Button(
    onClick = { viewModel.savePage() },
    colors = customColors ?: ButtonDefaults.buttonColors(),

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  60. modifier: Modifier = Modifier,
    Modifiers
    @Composable
    fun PageTitle(
    text: String,
    )

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  63. Modifiers
    @Composable
    fun PageTitle(
    text: String,
    )

    View full-size slide

  64. Modifiers
    @Composable
    fun PageTitle(
    text: String,
    )
    Box(
    Modifier
    .padding(12.dp)
    .background(Color.Blue)
    ) {
    PageTitle("Home")
    }

    View full-size slide

  65. 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),
    )

    View full-size slide

  66. Modifiers
    › Accept a Modifier parameter

    View full-size slide

  67. Modifiers
    › Accept a Modifier parameter
    › Named modifier

    View full-size slide

  68. Modifiers
    › Accept a Modifier parameter
    › Named modifier
    › First optional parameter

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  76. Modifiers
    @Composable
    fun MenuItems(
    smallScreen: Boolean,
    modifier: Modifier = Modifier,
    ) {
    if (smallScreen) {
    Column(modifier) {
    Text(...)
    Text(...)
    }
    } else {
    Row(modifier) {
    Text(...)
    Text(...)
    }
    }
    }

    View full-size slide

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

    View full-size slide

  78. Modifiers
    modifier: Modifier = Modifier

    View full-size slide

  79. 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"
    }
    }

    View full-size slide

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

    View full-size slide

  81. Modifiers
    @Composable
    fun Topics(
    topic: List,
    modifier: Modifier = Modifier,
    ) {
    Row(
    modifier = Modifier .then(modifier)
    ) {
    // ...
    }
    }
    .padding(12.dp) ,

    View full-size slide

  82. jb.gg/compose-modifiers

    View full-size slide

  83. Slots
    @Composable () -> Unit

    View full-size slide

  84. Slots
    @Composable
    fun Button(
    content: @Composable () -> Unit,
    )

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  93. Slot lifecycle
    val movableContent = remember(content) { movableContentOf(content) }
    @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

    View full-size slide

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

    View full-size slide

  95. 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-of

    View full-size slide

  96. Ordering parameters

    View full-size slide

  97. Ordering parameters
    › Required parameters
    › Optional parameters
    › First one: Modifier
    › Trailing content lambda

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  108. State
    @Composable
    fun Scroller(
    offset: MutableState< >,
    )
    Float

    View full-size slide

  109. State
    @Composable
    fun Scroller(
    offset: MutableState,
    )

    View full-size slide

  110. @Composable
    fun Scroller(
    offset: MutableState< >,
    ) {
    offset.value = 42f
    }
    State
    Float

    View full-size slide

  111. @Composable
    fun Scroller(
    offset: ,
    onOffsetChange: (Float) -> Unit,
    )
    State
    Float

    View full-size slide

  112. @Composable
    fun Scroller(
    offset: State< >,
    onOffsetChange: (Float) -> Unit,
    )
    State
    Float

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  115. @Composable
    fun Scroller(
    offset: Float,
    onOffsetChange: (Float) -> Unit,
    )
    State

    View full-size slide

  116. @Composable
    fun Scroller(
    offset: () -> Float,
    onOffsetChange: (Float) -> Unit,
    )
    State

    View full-size slide

  117. @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

    View full-size slide

  118. @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
    jb.gg/debugging-recomposition

    View full-size slide

  119. State holders

    View full-size slide

  120. State holders
    @Composable
    fun VerticalScroller(
    ,
    ,
    onScrollPositionChange: (Int) -> Unit,
    onScrollRangeChange: (Int) -> Unit,
    )
    scrollPosition: Int
    scrollRange: Int

    View full-size slide

  121. State holders
    @Composable
    fun VerticalScroller(
    )
    @Stable
    interface VerticalScrollerState {
    var
    var
    }
    scrollPosition: Int
    scrollRange: Int

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  124. State holders
    @Stable
    interface {
    }
    jb.gg/compose-stability
    @Composable
    fun VerticalScroller(
    verticalScrollerState: VerticalScrollerState
    )
    VerticalScrollerState
    scrollPosition: Int
    scrollRange: Int
    var
    var

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  131. Framework
    Library
    App

    View full-size slide

  132. 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
    Composing an API
    the way
    zsmb.co/talks
    Márton Braun
    @[email protected]

    View full-size slide