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

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.

Ragunath Jawahar

August 30, 2019
Tweet

More Decks by Ragunath Jawahar

Other Decks in Programming

Transcript

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

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

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

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

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

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

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

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

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

    in the framework require OS updates • Multiple tech stacks • Multiple sources of truth • Accidental complexity
  10. • Component-based UI framework • Declarative • Favours composition over

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

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

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

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

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

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

    inheritance • Unbundled UI Toolkit • Kotlin only • “Pre-Alpha” ⚠
  17. @Composable fun Text( text: String, style: TextStyle? = null, textAlign:

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

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

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

    TextAlign = DefaultTextAlign, // More parameters… ) { // More code… } Composable functions
  21. @Model data class Fruit( val name: String, val price: Double

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

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

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

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

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

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

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

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

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

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

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

    = 2.0, var quantity: Int = 0 ) { val totalPrice: Double get() = quantity * price }
  33. @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) } }
  34. @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) } }
  35. @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) } }
  36. @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) } }
  37. @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) } }
  38. @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) } }
  39. @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) } }
  40. @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) } }
  41. @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) } }
  42. @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) } }
  43. @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) } }
  44. @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) } }
  45. @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) } }
  46. @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) } }
  47. @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) } }
  48. @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) } }
  49. @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) } }
  50. @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) } }
  51. @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) } }
  52. @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) } }
  53. @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) } }
  54. @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) } }
  55. @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) } }
  56. @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) } }
  57. @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) } }
  58. @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) } }
  59. @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) } }
  60. @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 }) } } } }
  61. @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 }) } } } }
  62. @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 }) } } } }
  63. @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 }) } } } }
  64. @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 }) } } } }
  65. @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 }) } } } }
  66. @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 }) } } } }
  67. @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 }) } } } }
  68. @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 }) } } } }
  69. @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 }) } } } }
  70. @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 }) } } } }
  71. @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 }) } } } }
  72. @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 }) } } } }
  73. @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 }) } } } }
  74. @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 }) } } } }
  75. @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 }) } } } }
  76. @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 }) } } } }
  77. @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 }) } } } }
  78. @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 }) } } } }
  79. 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
  80. 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
  81. 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
  82. 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
  83. 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
  84. 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
  85. 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
  86. 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
  87. 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
  88. 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
  89. 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