$30 off During Our Annual Pro Sale. View Details »

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

Marton Braun
November 24, 2022

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/

Marton Braun

November 24, 2022
Tweet

More Decks by Marton Braun

Other Decks in Programming

Transcript

  1. Márton Braun @zsmb13
    Developer Relations Engineer
    Google
    Composing
    an API with
    Kotlin
    VOL.2

    View Slide

  2. Previously…
    goo.gle/composing-an-api-talk

    View Slide

  3. Jetpack Compose
    is Android’s modern
    toolkit for building
    native UI.
    d.android.com/compose

    View Slide

  4. Disclaimer
    Some of the code shown on these
    slides is simplified so that it fits.
    Check the implementation yourself!

    View Slide

  5. goo.gle/code-search-compose

    View Slide

  6. View Slide

  7. Default arguments

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. 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!",

    View Slide

  20. 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!",

    View Slide

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

    View Slide

  22. 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

    View Slide

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

    View Slide

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

    View Slide

  25. 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,
    ...,

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. Function types
    @Composable
    fun Button(
    onClick: () -> Unit,
    ...,
    content: @Composable RowScope.() -> Unit
    )
    Button(onClick = { onBackClick(screenId) }) {
    Text( )
    }
    stringResource(R.string.back)

    View Slide

  32. Function types

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  43. Function types
    checkNotNull(inputStream) { "Stream must be opened before reading" }
    require(offset >= 0) { "Offset must not be negative (was $offset)" }
    map.getOrElse(key) { fetchDefaultValue() }

    View Slide

  44. Delegates

    View Slide

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

    View Slide

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

    View Slide

  47. 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

    View Slide

  48. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  57. Delegates
    interface State {
    val value: T
    }
    interface MutableState : State {
    override var value: T
    ...
    }

    View Slide

  58. Delegates
    interface State {
    val value: T
    }
    interface MutableState : State {
    override var value: T
    ...
    }

    View Slide

  59. Delegates
    interface MutableState : State {
    override var value: T
    ...
    }
    interface State {
    val value: T
    }

    View Slide

  60. ...
    Delegates
    ) {
    this.value = value
    }
    interface MutableState : State {
    override var value: T
    ...
    }
    inline operator fun State.getValue( ): T = value
    inline operator fun MutableState.setValue(
    interface State {
    val value: T
    }
    value: T
    ...,

    View Slide

  61. ...,
    ...
    thisObj: Any?,
    property: KProperty<*>
    thisObj: Any?,
    property: KProperty<*>,
    ) {
    this.value = value
    }
    Delegates
    inline operator fun State.getValue(
    ): T = value
    inline operator fun MutableState.setValue(
    value: T

    View Slide

  62. Delegates
    val map = mutableMapOf()
    var name by map
    name = "Compose"
    println(map) // {name=Compose}

    View Slide

  63. Delegates
    val map = mutableMapOf()
    var name by map
    name = "Compose"
    println(map) // {name=Compose}

    View Slide

  64. Destructuring declarations

    View Slide

  65. Destructuring declarations
    val (r, g, b, a) = Color(0x00E97FFF)

    View Slide

  66. @JvmInline
    value class Color(val value: ULong) {
    ...
    }
    Destructuring declarations
    val (r, g, b, a) = Color(0x00E97FFF)

    View Slide

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

    View Slide

  68. ...
    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
    }

    View Slide

  69. @Composable
    fun NameInput() {
    var name by remember { mutableStateOf("") }
    OutlinedTextField(
    value = name,
    onValueChange = { name = it },
    label = { Text("Name") },
    )
    }
    Destructuring declarations
    MutableState

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  73. Destructuring declarations
    interface MutableState : State {
    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
    =

    View Slide

  74. Destructuring declarations
    interface MutableState : State {
    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
    =

    View Slide

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

    View Slide

  76. 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

    View Slide

  77. 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

    View Slide

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

    View Slide

  79. val (button, text) = createRefs()

    View Slide

  80. val (button, text) = createRefs()
    ConstrainedLayoutReference

    View Slide

  81. val (button, text) = createRefs()
    ConstrainedLayoutReferences
    ConstrainedLayoutReference

    View Slide

  82. 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++)
    ...
    }

    View Slide

  83. 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++)
    ...
    }

    View Slide

  84. 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++)
    ...
    }

    View Slide

  85. Strongly typed APIs

    View Slide

  86. Strongly typed APIs

    View Slide

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

    View Slide

  88. 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!

    View Slide

  89. 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
    ...

    View Slide

  90. 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
    ...

    View Slide

  91. 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!

    View Slide

  92. 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
    ...

    View Slide

  93. 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
    ...

    View Slide

  94. 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

    View Slide

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

    View Slide

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

    View Slide

  97. 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 {

    View Slide

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

    View Slide

  99. ● 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

    View Slide

  100. Thank You!
    Márton Braun
    @zsmb13
    Composing an API with Kotlin
    VOL.2

    View Slide