Slide 1

Slide 1 text

Composing an API the right way Márton Braun Developer Advocate

Slide 2

Slide 2 text

API design Framework Library App

Slide 3

Slide 3 text

API design Framework Library App

Slide 4

Slide 4 text

Guidelines

Slide 5

Slide 5 text

Guidelines

Slide 6

Slide 6 text

Guidelines jb.gg/compose

Slide 7

Slide 7 text

Guidelines jb.gg/compose

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Naming

Slide 10

Slide 10 text

Constants const val SOME_LOUD_CONSTANT = 999999

Slide 11

Slide 11 text

Constants const val SOME_LOUD_CONSTANT = 999999

Slide 12

Slide 12 text

Constants const val DefaultKeyName = "__defaultKey"

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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() }

Slide 15

Slide 15 text

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, }

Slide 16

Slide 16 text

Functions

Slide 17

Slide 17 text

Functions It depends.

Slide 18

Slide 18 text

Functions Return a value Emit content

Slide 19

Slide 19 text

Functions Return a value Emit content

Slide 20

Slide 20 text

Functions Return a value Emit content

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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(...)

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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") } } } }

Slide 35

Slide 35 text

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") } } } }

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Components Data UI

Slide 49

Slide 49 text

Parameters

Slide 50

Slide 50 text

CompositionLocal jb.gg/composition-locals

Slide 51

Slide 51 text

CompositionLocal jb.gg/composition-locals

Slide 52

Slide 52 text

CompositionLocal jb.gg/composition-locals

Slide 53

Slide 53 text

CompositionLocal jb.gg/composition-locals

Slide 54

Slide 54 text

CompositionLocal Data Styles jb.gg/composition-locals

Slide 55

Slide 55 text

> Parameters CompositionLocal explicit implicit

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

@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 )

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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(),

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Modifiers

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

Modifiers @Composable fun PageTitle( text: String, )

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

Modifiers › Accept a Modifier parameter

Slide 73

Slide 73 text

Modifiers › Accept a Modifier parameter › Named modifier

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

Modifiers modifier: Modifier = Modifier

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

jb.gg/compose-modifiers

Slide 90

Slide 90 text

Slots

Slide 91

Slide 91 text

Slots @Composable () -> Unit

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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"

Slide 97

Slide 97 text

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"

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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) }

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

Ordering parameters

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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 @Composable

Slide 108

Slide 108 text

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, ) @Composable

Slide 109

Slide 109 text

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, ) @Composable

Slide 110

Slide 110 text

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, ) ) { @Composable

Slide 111

Slide 111 text

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, ) ) { @Composable

Slide 112

Slide 112 text

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, ) { @Composable

Slide 113

Slide 113 text

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) } @Composable

Slide 114

Slide 114 text

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) } ) { @Composable

Slide 115

Slide 115 text

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 @Composable

Slide 116

Slide 116 text

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 @Composable

Slide 117

Slide 117 text

State

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

State @Composable fun Scroller( offset: MutableState, )

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

@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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

@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

Slide 128

Slide 128 text

@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

Slide 129

Slide 129 text

State holders

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

Framework Library App

Slide 142

Slide 142 text

Google I/O jb.gg/io24-compose-apis Simona Milanović

Slide 143

Slide 143 text

Tooling jb.gg/compose-rules

Slide 144

Slide 144 text

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/io24-compose-apis – jb.gg/compose-rules Composing an API the way zsmb.co/talks Márton Braun @[email protected]