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

Imperative is dead, long live Declarative! - S.T.A.Y. Home #4

Imperative is dead, long live Declarative! - S.T.A.Y. Home #4

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

October 14, 2020
Tweet

Transcript

  1. Imperative is dead, long live Declarative S.T.A.Y. Home #4 -

    Marco Gomiero
  2. Marco Gomiero Tech Lead @ Uniwhere Co-Lead @ GDG Venezia

    S.T.A.Y. Home #4 - @marcoGomier > Twitter: @marcoGomier > Github: prof18 > Website: marcogomiero.com
  3. Hacker News Client Loading State S.T.A.Y. Home #4 - @marcoGomier

  4. Hacker News Client Error State S.T.A.Y. Home #4 - @marcoGomier

    Loading State
  5. Hacker News Client Success State S.T.A.Y. Home #4 - @marcoGomier

    Loading State Error State
  6. How to built it now? S.T.A.Y. Home #4 - @marcoGomier

  7. How to built it now? S.T.A.Y. Home #4 - @marcoGomier

    1. Define the view
  8. 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> S.T.A.Y. Home #4 - @marcoGomier
  9. Storyboard or Code 1. Define the view S.T.A.Y. Home #4

    - @marcoGomier
  10. How to built it now? S.T.A.Y. Home #4 - @marcoGomier

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

    progressBar = findViewById<ProgressBar>(R.id.progress_bar) S.T.A.Y. Home #4 - @marcoGomier
  12. 1. Define the view 2. Retrieve the views instances @IBOutlet

    weak var loader: UIActivityIndicatorView! S.T.A.Y. Home #4 - @marcoGomier
  13. How to built it now? S.T.A.Y. Home #4 - @marcoGomier

    1. Define the view 2. Retrieve the views instances 3. Add some code to handle a list
  14. 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>() { ""... } S.T.A.Y. Home #4 - @marcoGomier
  15. 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 S.T.A.Y. Home #4 - @marcoGomier
  16. How to built it now? S.T.A.Y. Home #4 - @marcoGomier

    1. Define the view 2. Retrieve the views instances 3. Add some code to handle a list 4. Add some logic
  17. 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 ""... } } S.T.A.Y. Home #4 - @marcoGomier
  18. How to built it now? S.T.A.Y. Home #4 - @marcoGomier

    1. Define the view 2. Retrieve the views instances 3. Add some code to handle a list 4. Add some logic
  19. Imperative S.T.A.Y. Home #4 - @marcoGomier • Lots of boilerplate

    and boring code • Errors and bugs prone • Hard to maintain • Not a single source of truth
  20. Imperative S.T.A.Y. Home #4 - @marcoGomier

  21. “ a programming paradigm that uses statements that change a

    program's state ” S.T.A.Y. Home #4 - @marcoGomier - Wikipedia
  22. What if I can declare the views and forget the

    update step? S.T.A.Y. Home #4 - @marcoGomier
  23. Declarative S.T.A.Y. Home #4 - @marcoGomier

  24. “ what the program should accomplish without specifying how the

    program should achieve the result. ” S.T.A.Y. Home #4 - @marcoGomier - Wikipedia
  25. state A S.T.A.Y. Home #4 - @marcoGomier

  26. state A state B

  27. S.T.A.Y. Home #4 - @marcoGomier

  28. state A S.T.A.Y. Home #4 - @marcoGomier

  29. state B S.T.A.Y. Home #4 - @marcoGomier

  30. S.T.A.Y. Home #4 - @marcoGomier

  31. UI = f( S.T.A.Y. Home #4 - @marcoGomier state)

  32. S.T.A.Y. Home #4 - @marcoGomier state • Describes the UI

    right now, i.e. what to show • Independent of the previous state[s] • If changes, the UI is redrawn
  33. S.T.A.Y. Home #4 - @marcoGomier state • Describes the UI

    right now, i.e. what to show • Independent of the previous state[s] • If changes, the UI is redrawn [with optimizations]
  34. S.T.A.Y. Home #4 - @marcoGomier Changes are handled by the

    magician system not by the developer
  35. How the magician system works S.T.A.Y. Home #4 - @marcoGomier

    1. Creates an abstract representation of the UI and renders it
  36. How the magician system works S.T.A.Y. Home #4 - @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
  37. How the magician system works S.T.A.Y. Home #4 - @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]
  38. How it works in details? S.T.A.Y. Home #4 - @marcoGomier

  39. It depends! S.T.A.Y. Home #4 - @marcoGomier

  40. React [Native] S.T.A.Y. Home #4 - @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!' } } } }
  41. React [Native] S.T.A.Y. Home #4 - @marcoGomier • Virtual DOM

    as representation of the UI • Diff comparison using reconciliation, an O(n) heuristic algorithm
  42. React [Native] S.T.A.Y. Home #4 - @marcoGomier • Virtual DOM

    as representation of the UI • Diff comparison using reconciliation, an O(n) heuristic algorithm reactjs.org/docs/reconciliation.html
  43. Flutter S.T.A.Y. Home #4 - @marcoGomier • Element Tree as

    representation of the UI
  44. Flutter S.T.A.Y. Home #4 - @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)
  45. Flutter S.T.A.Y. Home #4 - @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
  46. Jetpack Compose S.T.A.Y. Home #4 - @marcoGomier • Slot table

    as representation of the UI
  47. Jetpack Compose S.T.A.Y. Home #4 - @marcoGomier • Every node

    is cached with a key
  48. Jetpack Compose S.T.A.Y. Home #4 - @marcoGomier • Reuse the

    node unless it changed
  49. Jetpack Compose S.T.A.Y. Home #4 - @marcoGomier • Reuse the

    node unless it changed youtube.com/watch?v=6BRlI5zfCCk
  50. S.T.A.Y. Home #4 - @marcoGomier How to define a piece

    of UI?
  51. S.T.A.Y. Home #4 - @marcoGomier Component How to define a

    piece of UI?
  52. Component S.T.A.Y. Home #4 - @marcoGomier 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; } }
  53. S.T.A.Y. Home #4 - @marcoGomier Component How to define a

    piece of UI? Widget
  54. S.T.A.Y. Home #4 - @marcoGomier 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: ""... , ), ); } }
  55. S.T.A.Y. Home #4 - @marcoGomier Component Widget composable function How

    to define a piece of UI?
  56. S.T.A.Y. Home #4 - @marcoGomier Component Widget composable function composable

    function @Composable fun NewsCard(news: News) { Card( shape = RoundedCornerShape(16.dp), elevation = 8.dp, modifier = ".. ) { Column { Text(news.title) ""... } } }
  57. S.T.A.Y. Home #4 - @marcoGomier Component Widget composable function struct

    How to define a piece of UI?
  58. S.T.A.Y. Home #4 - @marcoGomier Component Widget composable function struct

    struct struct NewsCard: View { let news: News var body: some View { HStack { VStack(alignment: .leading) { Text(self.news.title) ""... } } } }
  59. S.T.A.Y. Home #4 - @marcoGomier Component Widget composable function struct

    How to define a piece of UI?
  60. S.T.A.Y. Home #4 - @marcoGomier Component Widget composable function struct

    How to define a piece of UI? With a function!
  61. S.T.A.Y. Home #4 - @marcoGomier Component Widget composable function struct

    How to define a piece of UI? With a function (of the state) !
  62. S.T.A.Y. Home #4 - @marcoGomier Pure Function

  63. S.T.A.Y. Home #4 - @marcoGomier fun sum(a: Int, b: Int):

    Int { return a + b }
  64. S.T.A.Y. Home #4 - @marcoGomier fun sum(a: Int, b: Int):

    Int { return a + b } fun addExpense(account: BackAccount, amount: Int) { account.balance = account.balance - amount }
  65. S.T.A.Y. Home #4 - @marcoGomier fun sum(a: Int, b: Int):

    Int { return a + b } fun addExpense(account: BackAccount, amount: Int) { account.balance = account.balance - amount }
  66. Pure Function S.T.A.Y. Home #4 - @marcoGomier • Returns the

    same value given the same input • Has no side effects
  67. S.T.A.Y. Home #4 - @marcoGomier Take some inputs and return

    a piece of UI
  68. UI = f(state) S.T.A.Y. Home #4 - @marcoGomier

  69. None
  70. S.T.A.Y. Home #4 - @marcoGomier fun checkComponent(isChecked: Boolean): MaterialSwitch {

    return MaterialSwitch ( selected = isChecked, onSelectionChange = { selection "-> settings.update(selection) } ) }
  71. 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) } ) } S.T.A.Y. Home #4 - @marcoGomier SwitchComponent isChecked State Holder
  72. fun checkComponent(isChecked: Boolean): MaterialSwitch { return MaterialSwitch ( selected =

    isChecked, onSelectionChange = { selection "-> settings.update(selection) } ) } S.T.A.Y. Home #4 - @marcoGomier SwitchComponent State Holder onSelectionChange
  73. S.T.A.Y. Home #4 - @marcoGomier SwitchComponent isChecked fun checkComponent(isChecked: Boolean):

    MaterialSwitch { return MaterialSwitch ( selected = isChecked, onSelectionChange = { selection "-> settings.update(selection) } ) }
  74. S.T.A.Y. Home #4 - @marcoGomier Properties goes down Events goes

    up
  75. S.T.A.Y. Home #4 - @marcoGomier

  76. S.T.A.Y. Home #4 - @marcoGomier Composition

  77. Inheritance S.T.A.Y. Home #4 - @marcoGomier Composition over

  78. S.T.A.Y. Home #4 - @marcoGomier Inheritance fun Container() : View()

    { ".. } fun NewsCard() : View() { ".. } fun NewsCardContainer() : "?? { ".. }
  79. S.T.A.Y. Home #4 - @marcoGomier Composition fun NewsCardContainer() { NewsCard

    { Container { ""... } } } fun Container() { ".. } fun NewsCard() { ".. }
  80. Imperative vs Declarative S.T.A.Y. Home #4 - @marcoGomier

  81. S.T.A.Y. Home #4 - @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() } }
  82. S.T.A.Y. Home #4 - @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
  83. S.T.A.Y. Home #4 - @marcoGomier when (appState"?.newsState) { 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() } }
  84. S.T.A.Y. Home #4 - @marcoGomier https://github.com/prof18/imperative-vs-declarative

  85. Conclusions S.T.A.Y. Home #4 - @marcoGomier

  86. Conclusions S.T.A.Y. Home #4 - @marcoGomier • Describe WHAT to

    see NOT HOW • Less code to write → Fewer bugs • Totally new paradigm, it requires time to get used to
  87. 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
  88. Thank you! > Twitter: @marcoGomier > Github: prof18 > Website:

    marcogomiero.com S.T.A.Y. Home #4 - Marco Gomiero