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

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. Shifting Paradigms Ragunath Jawahar / @ragunathjawahar

  2. One moment…

  3. None
  4. None
  5. None
  6. None
  7. None
  8. None
  9. None
  10. None
  11. Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  12. Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  13. Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  14. Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  15. Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  16. None
  17. Data Binding

  18. None
  19. None
  20. None
  21. Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  22. Hiccups • A decade old UI toolkit • Bug fixes

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

  25. Loading Show Results

  26. Loading Show Results Load More

  27. Loading Show Results Load More

  28. Loading Show Results Load More

  29. Loading Show Results Load More

  30. Progress Bar?

  31. Loading Show Results Load More

  32. Loading Show Results Load More

  33. Loading Show Results Load More

  34. Loading Show Results Load More

  35. Loading Show Results Load More

  36. Loading Show Results Load More Loading Show Results Load More

  37. Loading Show Results Load More Loading Show Results Load More

  38. Loading Show Results Load More Loading Show Results Load More

  39. Loading Show Results Load More Loading Show Results Load More

  40. Loading Show Results Load More Loading Show Results Load More

  41. Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  42. Hiccups • A decade old UI toolkit • Bug fixes

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  43. Jetpack Compose

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

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⛔
  45. • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⛔
  46. • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⛔
  47. • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⛔
  48. • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⛔
  49. • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⚠
  50. • Component-based UI framework • Declarative • Favours composition over

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⚠
  51. Components 1. Compose Runtime 2. Compose Compiler Plugin 3. Compose

    UI
  52. Components 1. Compose Runtime 2. Compose Compiler Plugin 3. Compose

    UI
  53. Components 1. Compose Runtime 2. Compose Compiler Plugin 3. Compose

    UI
  54. Components 1. Compose Runtime 2. Compose Compiler Plugin 3. Compose

    UI
  55. Components 1. Compose Runtime 2. Compose Compiler Plugin 3. Compose

    UI
  56. Mental Model

  57. ui = f(s)

  58. ui = f(s)

  59. ui = f(s)

  60. ui = f(s)

  61. ui = f(s)

  62. No more references to views

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

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

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

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

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

  68. None
  69. None
  70. None
  71. None
  72. ui = f(s)

  73. ui = f(s)

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

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

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

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

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

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

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

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

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

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

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

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

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

  87. ui = f(s) ✅

  88. ui = f(s)

  89. ui = f(s)

  90. None
  91. Stepper

  92. @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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  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) } }
  119. Stepper

  120. Stepper ✅

  121. None
  122. Fruit Row

  123. @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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  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 }) } } } }
  142. Fruit Row

  143. Fruit Row ✅

  144. State?

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

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

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

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

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

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

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

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

    2.0) } FruitRow(apple) }
  153. 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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  163. None
  164. 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
  165. @ragunathjawahar Twitter / Medium / GitHub end;