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

Composing an API with Kotlin vol 2 (Advanced Ko...

Composing an API with Kotlin vol 2 (Advanced Kotlin Dev Day 2022)

Jetpack Compose is one of the best examples of an exciting Kotlin code base. It’s a real treasure trove of examples for using advanced Kotlin language features and conventions. In this talk, we’ll continue the exploration started in the prequel to learn more best practices around our favourite programming language.

More info and resources: https://zsmb.co/talks/composing-an-api-with-kotlin-vol-2/

Márton Braun

November 24, 2022
Tweet

More Decks by Márton Braun

Other Decks in Programming

Transcript

  1. Disclaimer Some of the code shown on these slides is

    simplified so that it fits. Check the implementation yourself!
  2. Default arguments fun RoundedCornerShape( topStart: Dp = 0.dp, topEnd: Dp

    = 0.dp, bottomEnd: Dp = 0.dp, bottomStart: Dp = 0.dp )
  3. Default arguments fun RoundedCornerShape( topStart: Dp = 0.dp, topEnd: Dp

    = 0.dp, bottomEnd: Dp = 0.dp, bottomStart: Dp = 0.dp ) @Stable inline val Int.dp: Dp get() = Dp(value = this.toFloat())
  4. Default arguments fun RoundedCornerShape( topStart: Dp = 0.dp, topEnd: Dp

    = 0.dp, bottomEnd: Dp = 0.dp, bottomStart: Dp = 0.dp ) RoundedCornerShape( topEnd = 16.dp, )
  5. Default arguments fun RoundedCornerShape( topStart: Dp = 0.dp, topEnd: Dp

    = 0.dp, bottomEnd: Dp = 0.dp, bottomStart: Dp = 0.dp ) RoundedCornerShape( topEnd = 16.dp, bottomStart = 32.dp, )
  6. Default arguments @Composable fun Text( text: String, modifier: Modifier =

    Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current )
  7. Default arguments @Composable fun Text( text: String, modifier: Modifier =

    Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current )
  8. Default arguments Text("Hello Compose!") @Composable fun Text( text: String, modifier:

    Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current )
  9. Default arguments Hello Compose! Text("Hello Compose!") @Composable fun Text( text:

    String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current )
  10. ) Default arguments Text( text = "Hello Compose!", fontSize =

    10.sp, Hello Compose! @Composable fun Text( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current )
  11. Text( text = "Hello Compose!", Default arguments Hello Compose! @Composable

    fun Text( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current ) fontSize = 10.sp, fontWeight = FontWeight.Bold, )
  12. modifier = Modifier.width(50.dp), Text( text = "Hello Compose!", Default arguments

    @Composable fun Text( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current ) Hello Compose! 50.dp fontSize = 10.sp, fontWeight = FontWeight.Bold, )
  13. Default arguments modifier = Modifier.width(50.dp), fontSize = 10.sp, fontWeight =

    FontWeight.Bold, ) Hello 50.dp @Composable fun Text( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current ) maxLines = 1, Text( text = "Hello Compose!",
  14. Default arguments Hello 50.dp @Composable fun Text( text: String, modifier:

    Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current ) modifier = Modifier.width(50.dp), fontSize = 10.sp, fontWeight = FontWeight.Bold, ) maxLines = 1, Text( text = "Hello Compose!",
  15. modifier = Modifier.width(50.dp), fontSize = 10.sp, fontWeight = FontWeight.Bold, )

    maxLines = 1, Text( text = "Hello Compose!", overflow = TextOverflow.Ellipsis, Default arguments Hello Co... 50.dp @Composable fun Text( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current )
  16. Default & named arguments Hello Co... @Composable fun Text( text:

    String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current ) modifier = Modifier.width(50.dp), fontSize = 10.sp, fontWeight = FontWeight.Bold, ) maxLines = 1, Text( text = "Hello Compose!", overflow = TextOverflow.Ellipsis, 50.dp
  17. Default & named 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, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable RowScope.() -> Unit )
  18. Default & named 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, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable RowScope.() -> Unit )
  19. Function types 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, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable RowScope.() -> Unit ) @Composable fun Button( onClick: () -> Unit, ...,
  20. m o d i f i e r : M

    o d i f i e r = M o d i f i e r , e n a b l e d : B o o l e a n = t r u e , s h a p e : S h a p e = B u t t o n D e f a u l t s . s h a p e , c o l o r s : B u t t o n C o l o r s = B u t t o n D e f a u l t s . b u t t o n C o l o r s ( ) , e l e v a t i o n : B u t t o n E l e v a t i o n ? = B u t t o n D e f a u l t s . b u t t o n E l e v a t i o n ( ) , b o r d e r : B o r d e r S t r o k e ? = n u l l , c o n t e n t P a d d i n g : P a d d i n g V a l u e s = B u t t o n D e f a u l t s . C o n t e n t P a d d i n g , i n t e r a c t i o n S o u r c e : M u t a b l e I n t e r a c t i o n S o u r c e = r e m e m b e r { M u t a b l e I n t e r a c t i o n S o u r c e ( ) } , Function types Button(onClick = { onBackClick(screenId) }) { Icon( imageVector = Icons.Rounded.ArrowBack, contentDescription = stringResource(R.string.back) ) } @Composable fun Button( onClick: () -> Unit, content: @Composable RowScope.() -> Unit ) ...,
  21. Function types @Composable fun Button( onClick: () -> Unit, ...,

    content: @Composable RowScope.() -> Unit ) Button(onClick = { onBackClick(screenId) }) { Icon( imageVector = Icons.Rounded.ArrowBack, contentDescription = stringResource(R.string.back) ) }
  22. Function types @Composable fun Button( onClick: () -> Unit, ...,

    content: @Composable RowScope.() -> Unit ) Button(onClick = { onBackClick(screenId) }) { Icon( imageVector = Icons.Rounded.ArrowBack, contentDescription = stringResource(R.string.back) ) }
  23. Function types @Composable fun Button( onClick: () -> Unit, ...,

    content: @Composable RowScope.() -> Unit ) Button(onClick = { onBackClick(screenId) }) { Icon( imageVector = Icons.Rounded.ArrowBack, contentDescription = stringResource(R.string.back) ) }
  24. Function types @Composable fun Button( onClick: () -> Unit, ...,

    content: @Composable RowScope.() -> Unit ) Button(onClick = { onBackClick(screenId) }) { Icon( imageVector = Icons.Rounded.ArrowBack, contentDescription = ) } stringResource(R.string.back)
  25. Function types @Composable fun Button( onClick: () -> Unit, ...,

    content: @Composable RowScope.() -> Unit ) Button(onClick = { onBackClick(screenId) }) { Text( ) } stringResource(R.string.back)
  26. Function types @Composable fun Title(snack: Snack, scroll: Int) { val

    offset = (maxOffset - scroll).coerceAtLeast(minOffset) Column( modifier = Modifier.graphicsLayer(translationY = offset) ) { Text(...) Text(...) Text(...) } } Title(snack, scroll.value)
  27. Function types @Composable fun Title(snack: Snack, scroll: Int) { val

    offset = (maxOffset - scroll).coerceAtLeast(minOffset) Column( modifier = Modifier.graphicsLayer(translationY = offset) ) { Text(...) Text(...) Text(...) } } Title(snack, scroll.value)
  28. Function types @Composable fun Title(snack: Snack, scroll: Int) { val

    offset = (maxOffset - scroll).coerceAtLeast(minOffset) Column( modifier = Modifier.graphicsLayer(translationY = offset) ) { Text(...) Text(...) Text(...) } } Title(snack, scroll.value)
  29. Function types @Composable fun Title(snack: Snack, scroll: Int) { val

    offset = (maxOffset – scroll Column( modifier = Modifier.graphicsLayer(translationY = offset) ) { Text(...) Text(...) Text(...) } } Title(snack, scroll.value) ).coerceAtLeast(minOffset)
  30. @Composable fun Title(snack: Snack, scrollProvider: () -> Int) { val

    offset = (maxOffset – scrollProvider() Column( modifier = Modifier.graphicsLayer(translationY = offset) ) { Text(...) Text(...) Text(...) } } Title(snack, { scroll.value } Function types ) ).coerceAtLeast(minOffset)
  31. Function types @Composable fun Title(snack: Snack, scrollProvider: () -> Int)

    { val offset = (maxOffset – scrollProvider()).coerceAtLeast(minOffset) Column( modifier = Modifier.graphicsLayer(translationY = offset) ) { Text(...) Text(...) Text(...) } } Title(snack, { scroll.value })
  32. Function types @Composable fun Title(snack: Snack, scrollProvider: () -> Int)

    { val offset = (maxOffset – scrollProvider()) Column( modifier = Modifier.graphicsLayer( ) ) { Text(...) Text(...) Text(...) } } Title(snack, { scroll.value }) translationY = offset .coerceAtLeast(minOffset)
  33. Function types @Composable fun Title(snack: Snack, scrollProvider: () -> Int)

    { Column( modifier = Modifier.graphicsLayer { } ) { Text(...) Text(...) Text(...) } } Title(snack, { scroll.value }) translationY = offset .coerceAtLeast(minOffset) val offset = (maxOffset - scrollProvider())
  34. Function types @Composable fun Title(snack: Snack, scrollProvider: () -> Int)

    { Column( modifier = Modifier.graphicsLayer { translationY = offset } ) { Text(...) Text(...) Text(...) } } Title(snack, { scroll.value }) .coerceAtLeast(minOffset) val offset = (maxOffset - scrollProvider())
  35. Function types @Composable fun Title(snack: Snack, scrollProvider: () -> Int)

    { Column( modifier = Modifier.graphicsLayer { translationY = offset } ) { Text(...) Text(...) Text(...) } } Title(snack, { scroll.value }) .coerceAtLeast(minOffset) val offset = (maxOffset - scrollProvider())
  36. Function types checkNotNull(inputStream) { "Stream must be opened before reading"

    } require(offset >= 0) { "Offset must not be negative (was $offset)" } map.getOrElse(key) { fetchDefaultValue() }
  37. Delegates val pi by lazy { sqrt(6 * (1..1_000_000_000).sumOf {

    1.0 / it / it }) } fun main() { println("I really like $pi") println("Twice the pi, double the flavour:") println(pi * 2) }
  38. Delegates val pi by lazy { sqrt(6 * (1..1_000_000_000).sumOf {

    1.0 / it / it }) } fun main() { println("I really like $pi") println("Twice the pi, double the flavour:") println(pi * 2) }
  39. Delegates val pi by lazy { sqrt(6 * (1..1_000_000_000).sumOf {

    1.0 / it / it }) } fun main() { println("I really like $pi") println("Twice the pi, double the flavour:") println(pi * 2) } I really like 3.14159264498239 Twice the pi, double the flavour: 6.28318528996478 operator fun getValue(thisRef: T, property: KProperty<*>): V
  40. Delegates val pi by lazy { sqrt(6 * (1..1_000_000_000).sumOf {

    1.0 / it / it }) } fun main() { println("I really like $pi") println("Twice the pi, double the flavour:") println(pi * 2) } operator fun getValue(thisRef: T, property: KProperty<*>): V
  41. Delegates val pi by lazy { sqrt(6 * (1..1_000_000_000).sumOf {

    1.0 / it / it }) } fun main() { println("I really like $pi") println("Twice the pi, double the flavour:") println(pi * 2) } operator fun getValue(thisRef: T, property: KProperty<*>): V operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
  42. Delegates val pi by lazy { sqrt(6 * (1..1_000_000_000).sumOf {

    1.0 / it / it }) } fun main() { println("I really like $pi") println("Twice the pi, double the flavour:") println(pi * 2) } operator fun getValue(thisRef: T, property: KProperty<*>): V operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
  43. Delegates fun main() { val pi by lazy { sqrt(6

    * (1..1_000_000_000).sumOf { 1.0 / it / it }) } println("I really like $pi") println("Twice the pi, double the flavour:") println(pi * 2) } operator fun getValue(thisRef: T, property: KProperty<*>): V operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
  44. Delegates @Composable fun NameInput() { val name = remember {

    mutableStateOf("") } OutlinedTextField( value = name.value, onValueChange = { name.value = it }, label = { Text("Name") } ) }
  45. @Composable fun NameInput() { val name = remember { mutableStateOf("")

    } OutlinedTextField( value = name.value, onValueChange = { name.value = it }, label = { Text("Name") } ) } Delegates MutableState<String>
  46. @Composable fun NameInput() { val name = remember { mutableStateOf("")

    } OutlinedTextField( value = name.value, onValueChange = { name.value = it }, label = { Text("Name") } ) } Delegates MutableState<String>
  47. Delegates @Composable fun NameInput() { var name: String by remember

    { mutableStateOf("") } OutlinedTextField( value = name.value, onValueChange = { name.value = it }, label = { Text("Name") } ) } MutableState<String>
  48. Delegates @Composable fun NameInput() { var name: String by remember

    { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } ) } MutableState<String>
  49. Delegates interface State<out T> { val value: T } interface

    MutableState<T> : State<T> { override var value: T ... }
  50. Delegates interface State<out T> { val value: T } interface

    MutableState<T> : State<T> { override var value: T ... }
  51. Delegates interface MutableState<T> : State<T> { override var value: T

    ... } interface State<out T> { val value: T }
  52. ... Delegates ) { this.value = value } interface MutableState<T>

    : State<T> { override var value: T ... } inline operator fun <T> State<T>.getValue( ): T = value inline operator fun <T> MutableState<T>.setValue( interface State<out T> { val value: T } value: T ...,
  53. ..., ... thisObj: Any?, property: KProperty<*> thisObj: Any?, property: KProperty<*>,

    ) { this.value = value } Delegates inline operator fun <T> State<T>.getValue( ): T = value inline operator fun <T> MutableState<T>.setValue( value: T
  54. Delegates val map = mutableMapOf<String, String>() var name by map

    name = "Compose" println(map) // {name=Compose}
  55. Delegates val map = mutableMapOf<String, String>() var name by map

    name = "Compose" println(map) // {name=Compose}
  56. @JvmInline value class Color(val value: ULong) { ... } Destructuring

    declarations val (r, g, b, a) = Color(0x00E97FFF)
  57. Destructuring declarations val (r, g, b, a) = Color(0x00E97FFF) @JvmInline

    value class Color(val value: ULong) { val red: Float get() { ... } val green: Float get() { ... } val blue: Float get() { ... } val alpha: Float get() { ... } ... }
  58. ... Destructuring declarations val (r, g, b, a) = Color(0x00E97FFF)

    @JvmInline value class Color(val value: ULong) { val red: Float get() { ... } val green: Float get() { ... } val blue: Float get() { ... } val alpha: Float get() { ... } operator fun component1(): Float = red operator fun component2(): Float = green operator fun component3(): Float = blue operator fun component4(): Float = alpha }
  59. @Composable fun NameInput() { var name by remember { mutableStateOf("")

    } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") }, ) } Destructuring declarations MutableState<String>
  60. Destructuring declarations @Composable fun NameInput() { val (name, setName) remember

    { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") }, ) } = MutableState<String>
  61. Destructuring declarations @Composable fun NameInput() { val (name: String, setName:

    (String) -> Unit) remember { mutableStateO OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") }, ) } MutableState<String> =
  62. Destructuring declarations @Composable fun NameInput() { val (name: String, setName:

    (String) -> Unit) remember { mutableStateO OutlinedTextField( value = name, onValueChange = setName, label = { Text("Name") }, ) } MutableState<String> =
  63. Destructuring declarations interface MutableState<T> : State<T> { override var value:

    T operator fun component1(): T = value operator fun component2(): (T) -> Unit = { value = it } } @Composable fun NameInput() { val (name: String, setName: (String) -> Unit) remember { mutableStateO OutlinedTextField( value = name, onValueChange = setName, label = { Text("Name") }, ) } MutableState<String> =
  64. Destructuring declarations interface MutableState<T> : State<T> { override var value:

    T operator fun component1(): T = value operator fun component2(): (T) -> Unit = { value = it } } @Composable fun NameInput() { val (name: String, setName: (String) -> Unit) remember { mutableStateO OutlinedTextField( value = name, onValueChange = setName, label = { Text("Name") }, ) } MutableState<String> =
  65. Destructuring declarations ConstraintLayout { val (button, text) = createRefs() Button(

    onClick = { /* Do something */ }, modifier = Modifier.constrainAs(button) { top.linkTo(parent.top, margin = 16.dp) } ) { Text("Button") } Text("Text", Modifier.constrainAs(text) { top.linkTo(button.bottom, margin = 16.dp) }) }
  66. ConstraintLayout { val (button, text) = createRefs() Button( onClick =

    { /* Do something */ }, modifier = Modifier.constrainAs(button) { top.linkTo(parent.top, margin = 16.dp) } ) { Text("Button") } Text("Text", Modifier.constrainAs(text) { top.linkTo(button.bottom, margin = 16.dp) }) } Destructuring declarations Button(ref = button) Text(ref = text) 16.dp
  67. ConstraintLayout { val (button, text) = createRefs() Button( onClick =

    { /* Do something */ }, modifier = Modifier.constrainAs(button) { top.linkTo(parent.top, margin = 16.dp) } ) { Text("Button") } Text("Text", Modifier.constrainAs(text) { top.linkTo(button.bottom, margin = 16.dp) }) } Destructuring declarations
  68. ConstraintLayout { Button( onClick = { /* Do something */

    }, modifier = Modifier.constrainAs(button) { top.linkTo(parent.top, margin = 16.dp) } ) { Text("Button") } Text("Text", Modifier.constrainAs(text) { top.linkTo(button.bottom, margin = 16.dp) }) } this: ConstraintLayoutScope this: ConstrainScope this: ConstrainScope Destructuring declarations val (button, text) = createRefs()
  69. val (button, text) = createRefs() ConstrainedLayoutReferences ConstrainedLayoutReference @LayoutScopeMarker class ConstraintLayoutScope

    { fun createRefs() = referencesObject private var referencesObject: ConstrainedLayoutReferences = ... inner class ConstrainedLayoutReferences internal constructor() { operator fun component1() = createRef() operator fun component2() = createRef() operator fun component3() = createRef() operator fun component4() = createRef() ... operator fun component16() = createRef() } fun createRef() = ConstrainedLayoutReference(childId++) ... }
  70. val (button, text) = createRefs() ConstrainedLayoutReferences ConstrainedLayoutReference @LayoutScopeMarker class ConstraintLayoutScope

    { fun createRefs() = referencesObject private var referencesObject: ConstrainedLayoutReferences = ... inner class ConstrainedLayoutReferences internal constructor() { operator fun component1() = createRef() operator fun component2() = createRef() operator fun component3() = createRef() operator fun component4() = createRef() ... operator fun component16() = createRef() } fun createRef() = ConstrainedLayoutReference(childId++) ... }
  71. val (button, text) = createRefs() ConstrainedLayoutReferences ConstrainedLayoutReference @LayoutScopeMarker class ConstraintLayoutScope

    { fun createRefs() = referencesObject private var referencesObject: ConstrainedLayoutReferences = ... inner class ConstrainedLayoutReferences internal constructor() { operator fun component1() = createRef() operator fun component2() = createRef() operator fun component3() = createRef() operator fun component4() = createRef() ... operator fun component16() = createRef() } fun createRef() = ConstrainedLayoutReference(childId++) ... }
  72. Strongly typed APIs Box { Text( text = "Hello Compose!",

    modifier = Modifier.align(Alignment. ) ) } Column { Text( text = "Hello Compose!", modifier = Modifier.align(Alignment. ) ) } CenterHorizontally BottomStart
  73. Strongly typed APIs Box { Text( text = "Hello Compose!",

    modifier = Modifier.align(Alignment. ) ) } Column { Text( text = "Hello Compose!", modifier = Modifier.align(Alignment. ) ) } CenterHorizontally BottomStart Hello Compose!
  74. Strongly typed APIs Box { Text( text = "Hello Compose!",

    modifier = Modifier.align(Alignment. ) ) } Column { Text( text = "Hello Compose!", modifier = Modifier.align(Alignment. ) ) } CenterHorizontally Hello Compose! BottomStart TopCenter CenterEnd ...
  75. Strongly typed APIs Box { Text( text = "Hello Compose!",

    modifier = Modifier.align(Alignment. ) ) } Column { Text( text = "Hello Compose!", modifier = Modifier.align(Alignment. ) ) } CenterHorizontally Hello Compose! Hello Compose! BottomStart TopCenter CenterEnd ...
  76. Strongly typed APIs Box { Text( text = "Hello Compose!",

    modifier = Modifier.align(Alignment. ) ) } Column { Text( text = "Hello Compose!", modifier = Modifier.align(Alignment. ) ) } CenterHorizontally Start End BottomStart TopCenter CenterEnd ... Hello Compose! Hello Compose!
  77. Strongly typed APIs Box { Text( text = "Hello Compose!",

    modifier = Modifier.align(Alignment. ) ) } Column { Text( text = "Hello Compose!", modifier = Modifier.align(Alignment. ) ) } CenterHorizontally Start End BottomStart TopCenter CenterEnd ...
  78. Strongly typed APIs Box { Text( text = "Hello Compose!",

    modifier = Modifier.align(Alignment. ) ) } Column { Text( text = "Hello Compose!", modifier = Modifier.align(Alignment. ) ) } this: BoxScope this: ColumnScope CenterHorizontally Start End BottomStart TopCenter CenterEnd ...
  79. Strongly typed APIs Box { Text( text = "Hello Compose!",

    modifier = Modifier.align(Alignment. ) ) } Column { Text( text = "Hello Compose!", modifier = Modifier.align(Alignment. ) ) } this: BoxScope CenterHorizontally Start End BottomStart TopCenter CenterEnd ... Alignment this: ColumnScope Alignment.Horizontal
  80. Strongly typed APIs Box { Text( text = "Hello Compose!",

    modifier = Modifier.align(Alignment. ) ) } Column { Text( text = "Hello Compose!", modifier = Modifier.align(Alignment. ) ) } this: BoxScope Alignment CenterHorizontally BottomStart Alignment.Horizontal this: ColumnScope
  81. ... ... @Stable fun interface Alignment { fun align(size: IntSize,

    space: IntSize, layoutDirection: LayoutDirection): IntOffset @Stable fun align(size: Int, space: Int, layoutDirection: LayoutDirection): Int } @Stable fun align(size: Int, space: Int): Int } } Strongly typed APIs fun interface Horizontal { fun interface Vertical { // 1D Alignment.Horizontals val Start: Horizontal = BiasAlignment.Horizontal(-1f)
  82. companion object { // 2D Alignments val TopStart: Alignment =

    BiasAlignment(-1f, -1f) val TopCenter: Alignment = BiasAlignment(0f, -1f) val TopEnd: Alignment = BiasAlignment(1f, -1f) val CenterStart: Alignment = BiasAlignment(-1f, 0f) val Center: Alignment = BiasAlignment(0f, 0f) val CenterEnd: Alignment = BiasAlignment(1f, 0f) val BottomStart: Alignment = BiasAlignment(-1f, 1f) val BottomCenter: Alignment = BiasAlignment(0f, 1f) val BottomEnd: Alignment = BiasAlignment(1f, 1f) // ... } ... ... // 1D Alignment.Horizontals val Start: Horizontal = BiasAlignment.Horizontal(-1f) fun interface Alignment { } } } Strongly typed APIs fun interface Horizontal { fun interface Vertical {
  83. val TopStart: Alignment = BiasAlignment(-1f, -1f) val TopCenter: Alignment =

    BiasAlignment(0f, -1f) val TopEnd: Alignment = BiasAlignment(1f, -1f) val CenterStart: Alignment = BiasAlignment(-1f, 0f) val Center: Alignment = BiasAlignment(0f, 0f) val CenterEnd: Alignment = BiasAlignment(1f, 0f) val BottomStart: Alignment = BiasAlignment(-1f, 1f) val BottomCenter: Alignment = BiasAlignment(0f, 1f) val BottomEnd: Alignment = BiasAlignment(1f, 1f) fun interface Alignment { fun interface Horizontal { ... } fun interface Vertical { ... } companion object { // 2D Alignments // ... } } Strongly typed APIs // 1D Alignment.Horizontals val Start: Horizontal = BiasAlignment.Horizontal(-1f) val CenterHorizontally: Horizontal = BiasAlignment.Horizontal(0f) val End: Horizontal = BiasAlignment.Horizontal(1f) // 1D Alignment.Verticals val Top: Vertical = BiasAlignment.Vertical(-1f) val CenterVertically: Vertical = BiasAlignment.Vertical(0f) val Bottom: Vertical = BiasAlignment.Vertical(1f)
  84. • Composing an API with Kotlin vol. 1 • Jetpack

    Compose: Debugging Recomposition • Code search: Jetpack Compose • Compose API guidelines • Kotlin for Jetpack Compose goo.gle/composing-an-api-talk goo.gle/compose-debug-recomposition goo.gle/code-search-compose goo.gle/compose-api-guidelines goo.gle/kotlin-for-compose Resources