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

https://info.jetbrains.com/kotlin-everywhere-bangalore-2019.html

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

June 22, 2019
Tweet

Transcript

  1. Jetpack @Compose Ragunath Jawahar / @ragunathjawahar

  2. Many years ago…

  3. None
  4. Architecture

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

  6. View Box Box

  7. ✅ Separation of concerns ✅ Maintainability ✅ Extensibility ✅ Testability

    Boilerplate ⚠
  8. Boilerplate ⚠

  9. Jetpack Compose

  10. • Unbundled UI Toolkit • React, Litho, Vue.js & Flutter

    • Declarative • Composition over inheritance • Kotlin only • “Pre-Alpha” ⛔
  11. FRAMEWORKS ARE NOT TOOLS FOR ORGANISING YOUR CODE, THEY ARE

    TOOLS FOR ORGANISING YOUR MIND – Rick Hickey
  12. (1/2) UIs are trees

  13. (2/2) ui = f(s)

  14. (2/2) ui = f(s)

  15. (2/2) ui = f(s)

  16. (2/2) ui = f(s)

  17. (2/2) ui = f(s)

  18. A Tale of (at least) Two States

  19. None
  20. ui = f(s)

  21. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  22. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  23. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  24. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  25. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  26. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  27. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  28. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  29. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  30. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  31. // Android framework val textView = TextView(context) println(textView=>class.java) // AB

    android.widget.TextView // Jetpack Compose val text = Text(text = "Hello World!") println(text=>class.java) // AB kotlin.Unit No more reference to views
  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. @Composable fun Text( text: String, style: TextStyle? = null, textAlign:

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

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

  37. None
  38. None
  39. None
  40. None
  41. ui = f(s)

  42. ui = f(s)

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

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

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

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

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

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

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

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

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

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

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

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

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

  56. ui = f(s) ✅

  57. ui = f(s)

  58. ui = f(s)

  59. None
  60. Stepper

  61. @Composable fun Stepper(count: Int, onCountChange: (Int) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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) ^> 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. @Composable fun Stepper(count: Int, onCountChange: (Int) ^> 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) } }
  87. @Composable fun Stepper(count: Int, onCountChange: (Int) ^> 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) } }
  88. Stepper

  89. Stepper ✅

  90. None
  91. Fruit Row

  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. @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 }) } } } }
  110. @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 }) } } } }
  111. Fruit Row

  112. Fruit Row ✅

  113. State?

  114. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { FruitApp {

    val apple = +memo { Fruit("Apple") } FruitRow(apple) } } }
  115. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { FruitApp {

    val apple = +memo { Fruit("Apple") } FruitRow(apple) } } }
  116. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { FruitApp {

    val apple = +memo { Fruit("Apple") } FruitRow(apple) } } }
  117. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { FruitApp {

    val apple = +memo { Fruit("Apple") } FruitRow(apple) } } }
  118. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { FruitApp {

    val apple = +memo { Fruit("Apple") } FruitRow(apple) } } }
  119. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { FruitApp {

    val apple = +memo { Fruit("Apple") } FruitRow(apple) } } }
  120. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { FruitApp {

    val apple = +memo { Fruit("Apple") } FruitRow(apple) } } }
  121. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { FruitApp {

    val apple = +memo { Fruit("Apple") } FruitRow(apple) } } }
  122. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { FruitApp {

    val apple = +memo { Fruit("Apple") } FruitRow(apple) } } }
  123. { Demo }

  124. Future • Increased productivity • Higher-level of abstraction • Wider

    use of existing of skillset • Collaboration • Design • Product • Other platforms?
  125. None
  126. 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/
  127. @ragunathjawahar Twitter / Medium / GitHub end;