Shifting Paradigms

Shifting Paradigms

Droidjam 2019, Radisson Blu, Bengaluru

Reactive applications are the norm on the web. Android applications have begun increasingly reactive using architectures like MVVM, MVI, Redux, etc., The reactive nature of these applications are due to the underlying architecture rather than the UI framework itself. This has resulted in some big wins like testability and strong separation of concerns while trading-off attributes like time-to-market and quickly on-boarding new developers to an existing project.

The API surface of the Android UI framework has been the same for the past decade. This was largely because of the limitations imposed by old tooling and the programming language. With Kotlin bringing in DSLs, functional programming and compiler plugin capabilities - the members of the Android UI Toolkit team have rolled up their sleeves and are taking a new direction. The new UI toolkit is backed by principles and techniques like - one-way data flow, function composition, declarative programming, optimistic concurrency control, isolation of side-effects, etc.,

This shift in API design also demands a shift in thinking about the app architecture and building UIs. In this talk, we’ll see how Jetpack Compose is radically different from the existing Android UI framework and how it adopts several ideas and programming patterns from web frameworks like React, MobX, and Vue.js. We will also see examples of these patterns and use them to build Android apps.

A8ee6ea52b89dfa1388b592a260c60a6?s=128

Ragunath Jawahar

August 30, 2019
Tweet

Transcript

  1. 3.
  2. 4.
  3. 5.
  4. 6.
  5. 7.
  6. 8.
  7. 9.
  8. 10.
  9. 11.

    Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  10. 12.

    Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  11. 13.

    Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  12. 14.

    Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  13. 15.

    Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  14. 16.
  15. 18.
  16. 19.
  17. 20.
  18. 21.

    Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  19. 22.

    Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  20. 23.
  21. 24.
  22. 41.

    Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  23. 42.

    Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  24. 44.

    • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⛔
  25. 45.

    • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⛔
  26. 46.

    • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⛔
  27. 47.

    • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⛔
  28. 48.

    • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⛔
  29. 49.

    • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⚠
  30. 50.

    • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⚠
  31. 57.
  32. 58.
  33. 59.
  34. 60.
  35. 61.
  36. 63.

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

    TextAlign = DefaultTextAlign, // More parameters… ) { // More code… } Composable functions
  37. 64.

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

    TextAlign = DefaultTextAlign, // More parameters… ) { // More code… } Composable functions
  38. 65.

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

    TextAlign = DefaultTextAlign, // More parameters… ) { // More code… } Composable functions
  39. 66.

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

    TextAlign = DefaultTextAlign, // More parameters… ) { // More code… } Composable functions
  40. 68.

  41. 69.
  42. 70.
  43. 71.
  44. 72.
  45. 73.
  46. 74.
  47. 75.

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  48. 76.

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  49. 77.

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  50. 78.

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  51. 79.

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  52. 80.

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  53. 81.

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  54. 82.

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

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

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  56. 84.

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  57. 85.

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  58. 86.

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  59. 87.
  60. 89.
  61. 90.
  62. 91.
  63. 92.
  64. 93.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 94.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 95.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 96.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 97.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 98.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 99.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 100.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 101.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 102.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 103.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 104.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 105.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 106.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 107.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 108.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 109.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 110.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 111.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 112.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 113.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 114.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 115.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 116.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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. 117.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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) } }
  89. 118.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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) } }
  90. 119.

    @Composable fun Stepper(count: Int, onCountChange: (Int) P> 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) } }
  91. 120.
  92. 122.
  93. 123.
  94. 124.

    @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. 125.

    @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. 126.

    @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. 127.

    @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. 128.

    @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. 129.

    @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. 130.

    @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. 131.

    @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. 132.

    @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. 133.

    @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. 134.

    @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. 135.

    @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. 136.

    @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. 137.

    @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. 138.

    @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. 139.

    @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. 140.

    @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. 141.

    @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 }) } } } }
  112. 142.

    @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 }) } } } }
  113. 143.
  114. 145.
  115. 154.

    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 stays as close to the root as possible • Declarative • Reduction in boilerplate
  116. 155.

    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 stays as close to the root as possible • Declarative • Reduction in boilerplate
  117. 156.

    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 stays as close to the root as possible • Declarative • Reduction in boilerplate
  118. 157.

    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 stays as close to the root as possible • Declarative • Reduction in boilerplate
  119. 158.

    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 stays as close to the root as possible • Declarative • Reduction in boilerplate
  120. 159.

    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 stays as close to the root as possible • Declarative • Reduction in boilerplate
  121. 160.

    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 stays as close to the root as possible • Declarative • Reduction in boilerplate
  122. 161.

    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 stays as close to the root as possible • Declarative • Reduction in boilerplate
  123. 162.

    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 stays as close to the root as possible • Declarative • Reduction in boilerplate
  124. 163.

    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 stays as close to the root as possible • Declarative • Reduction in boilerplate
  125. 164.
  126. 165.

    Resources • http://intelligiblebabble.com/content-on-declarative-ui/ • http://intelligiblebabble.com/compose-from-first-principles/ • https://www.youtube.com/watch?v=4EFjDSijAZU • https://github.com/SimonSchubert/Braincup •

    https://developer.android.com/jetpack/compose • #compose channel at https://kotlinlang.slack.com/ • https://speakerdeck.com/benoberkfell/prototyping-with- jetpack-compose?slide=67