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

Jetpack Compose — Next Gen Kotlin UI Toolkit for Android

Jetpack Compose — Next Gen Kotlin UI Toolkit for Android

Droidcon Berlin '19
https://www.de.droidcon.com/schedule

The Android UI toolkit's API surface has remained the same for the past decade. With applications and UI becoming more and more demanding, we need a new programming model to handle this complexity. With Kotlin bringing in DSLs, functional programming, and compiler plugin capabilities - the Android UI Toolkit team is taking a new direction with Jetpack Compose.

This shift in API design also demands a shift in thinking about UI and app architecture. In this talk, we’ll see how Jetpack Compose is radically different from the existing Android UI framework and look at various examples to build reactive Android apps.

A8ee6ea52b89dfa1388b592a260c60a6?s=128

Ragunath Jawahar

July 02, 2019
Tweet

More Decks by Ragunath Jawahar

Other Decks in Programming

Transcript

  1. Jetpack Compose Next Gen UI Toolkit for Android Ragunath Jawahar

    / @ragunathjawahar
  2. Many years ago…

  3. None
  4. None
  5. None
  6. None
  7. Architecture

  8. MVP VIPER RIBs MVVM Redux MVC Grox MVI MVU BLoC

  9. View ? ?

  10. A Tale of (at least) Two States

  11. None
  12. None
  13. None
  14. • Too much explicit state management • Too many tech

    stacks • Inheritance as a primary means to reuse code
  15. None
  16. Mental Model Asynchronous Programming

  17. • Get passenger using registered email • Get the list

    of bookings for the passenger ID
  18. Callbacks.kt airlineApi.getPassenger("j.doe@email.com", object : Callback<PassengerResponse> { override fun onSuccess(response: PassengerResponse)

    { airlineApi.getBookings(response.passenger.id, object : Callback<BookingsResponse> { override fun onSuccess(response: BookingsResponse) { /* do something with the bookings */ } override fun onError(exception: Exception) { /* handle error retrieving bookings */ } }) } override fun onError(exception: Exception) { /* handle error retrieving passenger */ } })
  19. airlineApi .getPassenger("j.doe@email.com") .flatMap { airlineApi.getBookings(it.passenger.id) } .subscribe( { /* do

    something with the bookings */ }, { /* handle error retrieving passenger (or) bookings */ } ) RxJava.kt
  20. CoroutineScope(Dispatchers.IO).launch { val response = airlineApi.getPassenger("j.doe@email.com") val bookings = airlineApi.getBookings(response.passenger.id)

    withContext(Dispatchers.Main) { /* do something with the bookings */ } } Coroutines.kt
  21. CoroutineScope(Dispatchers.IO).launch { val response = airlineApi.getPassenger("j.doe@email.com") val bookings = airlineApi.getBookings(response.passenger.id)

    withContext(Dispatchers.Main) { /* do something with the bookings */ } } Coroutines.kt
  22. FRAMEWORKS ARE NOT TOOLS FOR ORGANISING YOUR CODE, THEY ARE

    TOOLS FOR ORGANISING YOUR MIND – Rick Hickey
  23. Mental Model Compose

  24. ui = f(s)

  25. ui = f(s)

  26. ui = f(s)

  27. ui = f(s)

  28. ui = f(s)

  29. No more reference to views

  30. @Composable fun Text( text: String, style: TextStyle? = null, textAlign:

    TextAlign = DefaultTextAlign, // More parameters… ) { // More code… } Composable functions
  31. @Composable fun Text( text: String, style: TextStyle? = null, textAlign:

    TextAlign = DefaultTextAlign, // More parameters… ) { // More code… } Composable functions
  32. @Composable fun Text( text: String, style: TextStyle? = null, textAlign:

    TextAlign = DefaultTextAlign, // More parameters… ) { // More code… } Composable functions
  33. @Composable fun Text( text: String, style: TextStyle? = null, textAlign:

    TextAlign = DefaultTextAlign, // More parameters… ) { // More code… } Composable functions
  34. A Composed Example (pun intended)

  35. None
  36. None
  37. None
  38. None
  39. ui = f(s)

  40. ui = f(s)

  41. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  42. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  43. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  44. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  45. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  46. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  47. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  48. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  49. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  50. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  51. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  52. @Model data class Fruit( val name: String, val price: Double,

    var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  53. ui = f(s)

  54. ui = f(s) ✅

  55. ui = f(s)

  56. ui = f(s)

  57. None
  58. Stepper

  59. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  60. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  61. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  62. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  63. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  64. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  65. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  66. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  67. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  68. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  69. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  70. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  71. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  72. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  73. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  74. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  75. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  76. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  77. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  78. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  79. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  80. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  81. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  82. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  83. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  84. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  85. @Composable fun Stepper(count: Int, onCountChange: (Int) Z> Unit) { val

    onRemoveOne = { onCountChange(count - 1) } val onAddOne = { onCountChange(count + 1) } Row { Button(text = "-", onClick = if (count > 0) onRemoveOne else null) WidthSpacer(8.dp) Container(width = 24.dp) { Text(text = "$count", style = +themeTextStyle { body1 }) } WidthSpacer(8.dp) Button(text = "+", onClick = onAddOne) } }
  86. Stepper

  87. Stepper ✅

  88. None
  89. Fruit Row

  90. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  91. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  92. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  93. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  94. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  95. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  96. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  97. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  98. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  99. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  100. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  101. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  102. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  103. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  104. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  105. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  106. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  107. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  108. @Composable fun FruitRow(fruit: Fruit) { Padding(padding = EdgeInsets(left = 16.dp,

    right = 16.dp)) { FlexRow { expanded(1.0f) { Text(text = "${fruit.name} ($${fruit.totalPrice})", style = +themeTextStyle { h6 }) } inflexible { Stepper(fruit.quantity, onCountChange = { fruit.quantity = it }) } } } }
  109. Fruit Row

  110. Fruit Row ✅

  111. State?

  112. FruitApp { val apple = +memo { Fruit("Apple", price =

    2.0) } FruitRow(apple) }
  113. FruitApp { val apple = +memo { Fruit("Apple", price =

    2.0) } FruitRow(apple) }
  114. FruitApp { val apple = +memo { Fruit("Apple", price =

    2.0) } FruitRow(apple) }
  115. FruitApp { val apple = +memo { Fruit("Apple", price =

    2.0) } FruitRow(apple) }
  116. FruitApp { val apple = +memo { Fruit("Apple", price =

    2.0) } FruitRow(apple) }
  117. FruitApp { val apple = +memo { Fruit("Apple", price =

    2.0) } FruitRow(apple) }
  118. FruitApp { val apple = +memo { Fruit("Apple", price =

    2.0) } FruitRow(apple) }
  119. FruitApp { val apple = +memo { Fruit("Apple", price =

    2.0) } FruitRow(apple) }
  120. Observations • Smallest reusable unit is a function • Stateless

    widgets (“controlled components”) • Single source of truth • Structure of the code matches the structure of the view hierarchy • UI was built from leaf to node • State was pushed as close to the root as possible • Declarative • Reduction in boilerplate
  121. None
  122. Resources • https://developer.android.com/jetpack/compose • http://intelligiblebabble.com/content-on-declarative-ui/ • http://intelligiblebabble.com/compose-from-first-principles/ • #compose channel

    at https://kotlinlang.slack.com/
  123. @ragunathjawahar Twitter / Medium / GitHub end;