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

Imperative is dead, long live Declarative! - AppDevCon

Imperative is dead, long live Declarative! - AppDevCon

Nowadays, the mobile world has started to see larger adoption of the declarative style to build UI as opposed to the (not so) old imperative style. This pattern is borrowed from the web world, from frameworks like React and Vue.js and it started to appear in the mobile world first with React Native, then with Flutter and finally, it captured the attention of the “native world” with Jetpack Compose and Swift UI. In this talk, we will explore the declarative style of building UI compared with the imperative one. We will try to enter in this mindset by finding the differences and by looking at some examples of these patterns.

9da5d5cc4b6a9f28058152e28364b02a?s=128

Marco Gomiero

November 12, 2020
Tweet

Transcript

  1. Imperative is dead, long live Declarative Appdevcon - Marco Gomiero

  2. Marco Gomiero ! Tech Lead @ Uniwhere " # $

    Co-Lead @ GDG Venezia Appdevcon - @marcoGomier > Twitter: @marcoGomier > Github: prof18 > Website: marcogomiero.com
  3. Hacker News Client Success State Appdevcon - @marcoGomier Loading State

    Error State
  4. Appdevcon - @marcoGomier % How to built it now? &

  5. Appdevcon - @marcoGomier % How to built it now? &

    1. Define the view
  6. XML or Code 1. Define the view <androidx.constraintlayout.widget.ConstraintLayout ""# "$

    <ProgressBar android:id="@+id/progress_bar" android:layout_width="wrap_content" android:layout_height="wrap_content" ""# "$ <TextView android:id="@+id/error_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" ""# "$ <Button android:id="@+id/error_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Refresh" android:visibility="gone" ""# "$ <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" ""# "$ "%androidx.constraintlayout.widget.ConstraintLayout> Appdevcon - @marcoGomier
  7. Storyboard or Code 1. Define the view Appdevcon - @marcoGomier

  8. Appdevcon - @marcoGomier % How to built it now? &

    1. Define the view 2. Retrieve the views instances
  9. 1. Define the view 2. Retrieve the views instances val

    progressBar = findViewById<ProgressBar>(R.id.progress_bar) Appdevcon - @marcoGomier
  10. 1. Define the view 2. Retrieve the views instances @IBOutlet

    weak var loader: UIActivityIndicatorView! Appdevcon - @marcoGomier
  11. Appdevcon - @marcoGomier % How to built it now? &

    1. Define the view 2. Retrieve the views instances 3. Add some code to handle a list
  12. 1. Define the view 2. Retrieve the views instances 3.

    Add some code to handle a list RecyclerView.Adapter class NewsAdapter(var items: List<News> = listOf()) : RecyclerView.Adapter<NewsAdapter.ViewHolder>() { ""# } Appdevcon - @marcoGomier
  13. 1. Define the view 2. Retrieve the views instances 3.

    Add some code to handle a list extension MainViewController: UITableViewDelegate, UITableViewDataSource { ""# } Delegate and a DataSource Appdevcon - @marcoGomier
  14. Appdevcon - @marcoGomier % How to built it now? &

    1. Define the view 2. Retrieve the views instances 3. Add some code to handle a list 4. Add some logic
  15. Kotlin 1. Define the view 2. Retrieve the views instances

    3. Add some code to handle a list 4. Add some logic when (appState.newsState) { is NewsState.Loading "& { progressBar.visibility = View.VISIBLE ""# } is NewsState.Error "& { progressBar.visibility = View.GONE errorMessage.visibility = View.VISIBLE ""# } is NewsState.Success "& { progressBar.visibility = View.GONE errorMessage.visibility = View.GONE recyclerView.visibility = View.VISIBLE ""# } } Appdevcon - @marcoGomier
  16. ' Imperative Appdevcon - @marcoGomier • Lots of boilerplate and

    boring code • Errors and bugs prone • Not a single source of truth • Hard to maintain
  17. Appdevcon - @marcoGomier ' Imperative

  18. Appdevcon - @marcoGomier “ a programming paradigm that uses statements

    that change a program's state ” - Wikipedia
  19. Appdevcon - @marcoGomier ( What if I can declare the

    views and forget the update step?
  20. Appdevcon - @marcoGomier ) Declarative

  21. Appdevcon - @marcoGomier “ what the program should accomplish without

    specifying how the program should achieve the result. ” - Wikipedia
  22. Appdevcon - @marcoGomier state A

  23. state A state B

  24. Appdevcon - @marcoGomier *

  25. state A * Appdevcon - @marcoGomier

  26. Appdevcon - @marcoGomier state B *

  27. Appdevcon - @marcoGomier +

  28. Appdevcon - @marcoGomier UI = f(state)

  29. Appdevcon - @marcoGomier state • Describes the UI right now,

    i.e. what to show • Independent of the previous state[s] • If changes, the UI is redrawn
  30. Appdevcon - @marcoGomier Changes are handled by the magician system

    not by the developer *
  31. How the magician system works Appdevcon - @marcoGomier 1. Creates

    an abstract representation of the UI and renders it
  32. How the magician system works Appdevcon - @marcoGomier 1. Creates

    an abstract representation of the UI and renders it 2. When a change is made, it creates a new representation 3. Computes the differences between the two representations 4. Renders the differences [if any]
  33. Appdevcon - @marcoGomier ( How it works in details?

  34. Appdevcon - @marcoGomier , It depends!

  35. React [Native] Appdevcon - @marcoGomier • Virtual DOM as representation

    of the UI <button class='button button-blue'> <b> OK! "%b> "%button> { type: 'button', props: { className: 'button button-blue', children: { type: 'b', props: { children: 'OK!' } } } }
  36. React [Native] Appdevcon - @marcoGomier • Virtual DOM as representation

    of the UI • Diff comparison using reconciliation, an O(n) heuristic algorithm
  37. React [Native] Appdevcon - @marcoGomier • Virtual DOM as representation

    of the UI • Diff comparison using reconciliation, an O(n) heuristic algorithm reactjs.org/docs/reconciliation.html
  38. Flutter Appdevcon - @marcoGomier • Element Tree as representation of

    the UI
  39. Flutter Appdevcon - @marcoGomier • Element Tree as representation of

    the UI • Not a tree-diffing algorithm • For each element, the child list is examined independently (O(n) algorithm)
  40. Flutter Appdevcon - @marcoGomier • Element Tree as representation of

    the UI • Not a tree-diffing algorithm • For each element, the child list is examined independently (O(n) algorithm) flutter.dev/docs/resources/inside-flutter
  41. Jetpack Compose Appdevcon - @marcoGomier • Slot table as representation

    of the UI
  42. Jetpack Compose Appdevcon - @marcoGomier • Every node is cached

    with a key
  43. Jetpack Compose Appdevcon - @marcoGomier • Reuse the node unless

    it changed
  44. Jetpack Compose Appdevcon - @marcoGomier • Reuse the node unless

    it changed youtube.com/watch?v=6BRlI5zfCCk
  45. Appdevcon - @marcoGomier - How to define a piece of

    UI?
  46. Appdevcon - @marcoGomier Component - How to define a piece

    of UI?
  47. Component Component export default class NewsCard extends React.Component { constructor(props)

    { super(props); } render() { const { news } = this.props; if (news) { return ( <View style={styles.container}> <TouchableOpacity "' > <Text "' > <Text "' > "%TouchableOpacity> "%View> ); } return null; } } Appdevcon - @marcoGomier
  48. Appdevcon - @marcoGomier Component - How to define a piece

    of UI? Widget
  49. Component Widget Widget class NewsCardWidget extends StatelessWidget { final News

    news; NewsCardWidget({this.news}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(6.0), child: Card( elevation: 8.0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.0), ), child: ""# , ), ); } } Appdevcon - @marcoGomier
  50. Appdevcon - @marcoGomier Component Widget composable function - How to

    define a piece of UI?
  51. Component Widget composable function composable function @Composable fun NewsCard(news: News)

    { Card( shape = RoundedCornerShape(16.dp), elevation = 8.dp, modifier = "' ) { Column { Text(news.title) ""# } } } Appdevcon - @marcoGomier
  52. Appdevcon - @marcoGomier Component Widget composable function struct - How

    to define a piece of UI?
  53. Component Widget composable function struct struct struct NewsCard: View {

    let news: News var body: some View { HStack { VStack(alignment: .leading) { Text(self.news.title) ""# } } } } Appdevcon - @marcoGomier
  54. Appdevcon - @marcoGomier Component Widget composable function struct - How

    to define a piece of UI?
  55. Appdevcon - @marcoGomier Component Widget composable function struct - How

    to define a piece of UI? With a function!
  56. Appdevcon - @marcoGomier Component Widget composable function struct - How

    to define a piece of UI? With a function (of the state) !
  57. Appdevcon - @marcoGomier Pure Function

  58. Appdevcon - @marcoGomier fun sum(a: Int, b: Int): Int {

    return a + b } .
  59. Appdevcon - @marcoGomier . fun sum(a: Int, b: Int): Int

    { return a + b } fun addExpense(account: BackAccount, amount: Int) { account.balance = account.balance - amount }
  60. Appdevcon - @marcoGomier . fun sum(a: Int, b: Int): Int

    { return a + b } fun addExpense(account: BackAccount, amount: Int) { account.balance = account.balance - amount } /
  61. Pure Function Appdevcon - @marcoGomier • Returns the same value

    given the same input • Has no side effects
  62. Appdevcon - @marcoGomier Take some inputs and return a piece

    of UI
  63. Appdevcon - @marcoGomier UI = f(state)

  64. None
  65. Appdevcon - @marcoGomier fun checkComponent(isChecked: Boolean): MaterialSwitch { return MaterialSwitch

    ( selected = isChecked, onSelectionChange = { selection "& settings.update(selection) } ) }
  66. fun checkComponent(isChecked: Boolean): MaterialSwitch { return MaterialSwitch ( selected =

    isChecked, onSelectionChange = { selection "& settings.update(selection) } ) } fun checkComponent(isChecked: Boolean): MaterialSwitch { return MaterialSwitch ( selected = isChecked, onSelectionChange = { selection "& settings.update(selection) } ) } SwitchComponent isChecked State Holder Appdevcon - @marcoGomier
  67. fun checkComponent(isChecked: Boolean): MaterialSwitch { return MaterialSwitch ( selected =

    isChecked, onSelectionChange = { selection "& settings.update(selection) } ) } SwitchComponent State Holder onSelectionChange Appdevcon - @marcoGomier
  68. SwitchComponent isChecked fun checkComponent(isChecked: Boolean): MaterialSwitch { return MaterialSwitch (

    selected = isChecked, onSelectionChange = { selection "& settings.update(selection) } ) } Appdevcon - @marcoGomier
  69. Appdevcon - @marcoGomier Properties goes down Events goes up

  70. Appdevcon - @marcoGomier 0

  71. Appdevcon - @marcoGomier 0 Composition

  72. Inheritance Appdevcon - @marcoGomier 0 Composition over

  73. Appdevcon - @marcoGomier 0 Inheritance fun Container() : View() {

    "' } fun NewsCard() : View() { "' } fun NewsCardContainer() : "( { "' }
  74. Appdevcon - @marcoGomier 0 Composition fun NewsCardContainer() { NewsCard {

    Container { ""# } } } fun Container() { "' } fun NewsCard() { "' }
  75. Appdevcon - @marcoGomier Imperative vs Declarative

  76. Appdevcon - @marcoGomier Imperative when (appState.newsState) { is NewsState.Loading "&

    { progressBar.visibility = View.VISIBLE recyclerView.visibility = View.GONE errorMessage.visibility = View.GONE errorButton.visibility = View.GONE } is NewsState.Error "& { val errorState = appState.newsState as NewsState.Error recyclerView.visibility = View.GONE errorButton.visibility = View.VISIBLE errorMessage.visibility = View.VISIBLE errorMessage.text = errorState.reason errorButton.setOnClickListener { viewModel.loadData() } }
  77. Appdevcon - @marcoGomier recyclerView.visibility = View.GONE errorButton.visibility = View.VISIBLE errorMessage.visibility

    = View.VISIBLE errorMessage.text = errorState.reason errorButton.setOnClickListener { viewModel.loadData() } } is NewsState.Success "& { val successState = appState.newsState as NewsState.Success progressBar.visibility = View.GONE errorMessage.visibility = View.GONE errorButton.visibility = View.GONE recyclerView.visibility = View.VISIBLE adapter.items = successState.news adapter.notifyDataSetChanged() } } Imperative
  78. Appdevcon - @marcoGomier when (appState")newsState) { is NewsState.Loading "& {

    LoadingView() } is NewsState.Success "& { val successState = appState")newsState as NewsState.Success NewsList(newsList = successState.news) } is NewsState.Error "& { val errorState = appState")newsState as NewsState.Error ErrorView( reason = errorState.reason, onRefreshClick = { viewModel.loadData() }) } } Declarative when (appState.newsState) { is NewsState.Loading "& { progressBar.visibility = View.VISIBLE recyclerView.visibility = View.GONE errorMessage.visibility = View.GONE errorButton.visibility = View.GONE } is NewsState.Error "& { val errorState = appState.newsState as NewsState.Error recyclerView.visibility = View.GONE errorButton.visibility = View.VISIBLE errorMessage.visibility = View.VISIBLE errorMessage.text = errorState.reason errorButton.setOnClickListener { viewModel.loadData() } } is NewsState.Success "& { val successState = appState.newsState as NewsState.Success progressBar.visibility = View.GONE errorMessage.visibility = View.GONE errorButton.visibility = View.GONE recyclerView.visibility = View.VISIBLE adapter.items = successState.news adapter.notifyDataSetChanged() } }
  79. Appdevcon - @marcoGomier https://github.com/prof18/imperative-vs-declarative

  80. Appdevcon - @marcoGomier Conclusions

  81. Appdevcon - @marcoGomier Conclusions • Describe WHAT to see NOT

    HOW • Less code to write → Fewer bugs • Totally new paradigm, it requires time to get used to
  82. Bibliography / Useful Links • https:"*www.youtube.com/watch?v=zaSn_qA7Nb4 • https:"*www.youtube.com/watch?v=4EFjDSijAZU • https:"*www.youtube.com/watch?v=x7cQ3mrcKaY

    • https:"*www.youtube.com/watch?v=1uRC3hmKQnM • https:"*www.youtube.com/watch?v=eR4LjL1h6cE • https:"*overreacted.io/react-as-a-ui-runtime/ • https:"*rauchg.com/2015/pure-ui • http:"*intelligiblebabble.com/compose-from-first-principles/ • https:"*www.youtube.com/watch?v=6BRlI5zfCCk • https:"*www.youtube.com/watch?v=WqnR_XhEiVI • https:"*reactjs.org/docs/reconciliation.html • https:"*flutter.dev/docs/resources/inside-flutter • https:"*www.youtube.com/watch?v=rshXbwrrUxY • https:"*www.youtube.com/watch?v=_1_XkeFUUME • https:"*www.youtube.com/watch?v=aH7oWxfxpJY • https:"*medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050 • https:"*medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd
  83. Appdevcon - @marcoGomier Thank you! > Twitter: @marcoGomier > Github:

    prof18 > Website: marcogomiero.com