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

June 24, 2022
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

  1. > Twitter: @marcoGomier 
 > Github: prof18 
 > Website:

    marcogomiero.com Imperative is dead, long live Declarative 👨💻 Senior Android Engineer @ TIER 
 Google Developer Expert for Kotlin
  2. Hacker News Client Loading State Appdevcon - @marcoGomier

  3. Hacker News Client Error State Loading State Appdevcon - @marcoGomier

  4. Hacker News Client Success State Loading State Error State Appdevcon

    - @marcoGomier
  5. 👷 How to built it 👑? 🚧 Appdevcon - @marcoGomier

  6. 👷 How to built it 👑? 🚧 1. Define the

    view Appdevcon - @marcoGomier
  7. 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
  8. Storyboard or Code 1. Define the view Appdevcon - @marcoGomier

  9. 👷 How to built it 👑? 🚧 1. Define the

    view 2. Retrieve the views instances Appdevcon - @marcoGomier
  10. 1. Define the view 2. Retrieve the views instances val

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

    weak var loader: UIActivityIndicatorView! Appdevcon - @marcoGomier
  12. 👷 How to built it 👑? 🚧 1. Define the

    view 2. Retrieve the views instances 3. Add some code to handle a list Appdevcon - @marcoGomier
  13. 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
  14. 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
  15. 👷 How to built it 👑? 🚧 1. Define the

    view 2. Retrieve the views instances 3. Add some code to handle a list 4. Add some logic Appdevcon - @marcoGomier
  16. 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
  17. 👑 Imperative • Lots of boilerplate and boring code •

    Errors and bugs prone • Not a single source of truth • Hard to maintain Appdevcon - @marcoGomier
  18. 👑 Imperative Appdevcon - @marcoGomier

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

    program's state ” - Wikipedia Appdevcon - @marcoGomier
  20. state A Appdevcon - @marcoGomier

  21. state A state B

  22. state A 🧙 Appdevcon - @marcoGomier

  23. state B 🧙 Appdevcon - @marcoGomier

  24. 📣 Declarative Appdevcon - @marcoGomier

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

    program should achieve the result. ” - Wikipedia Appdevcon - @marcoGomier
  26. 🧐 Appdevcon - @marcoGomier

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

  28. state • Describes the UI right now, i.e. what to

    show • Independent of the previous state[s] • If changes, the UI is redrawn Appdevcon - @marcoGomier
  29. 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] Appdevcon - @marcoGomier
  30. Changes are handled by the magician system not by the

    developer 🧙 Appdevcon - @marcoGomier
  31. How the magician system works 1. Creates an abstract representation

    of the UI and renders it Appdevcon - @marcoGomier
  32. How the magician system works 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 Appdevcon - @marcoGomier
  33. How the magician system works 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] Appdevcon - @marcoGomier
  34. 🤔 How it works in details? Appdevcon - @marcoGomier

  35. 🤷 It depends! Appdevcon - @marcoGomier

  36. React [Native] • 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!' } } } } Appdevcon - @marcoGomier
  37. React [Native] • Virtual DOM as representation of the UI

    • Diff comparison using reconciliation, an O(n) heuristic algorithm Appdevcon - @marcoGomier
  38. 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
  39. Flutter • Element Tree as representation of the UI Appdevcon

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

    Not a tree-diffing algorithm • For each element, the child list is examined independently 
 (O(n) algorithm) Appdevcon - @marcoGomier
  41. 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
  42. Jetpack Compose • Slot table as representation of the UI

    Appdevcon - @marcoGomier
  43. Jetpack Compose • Every node is cached with a key

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

    - @marcoGomier
  45. Jetpack Compose Appdevcon - @marcoGomier • Reuse the node unless

    it changed youtube.com/watch?v=6BRlI5zfCCk
  46. 👨🎨 How to define a piece of UI? Appdevcon -

    @marcoGomier
  47. Component 👨🎨 How to define a piece of UI? Appdevcon

    - @marcoGomier
  48. 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
  49. Component 👨🎨 How to define a piece of UI? Widget

    Appdevcon - @marcoGomier
  50. 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
  51. Component Widget composable function 👨🎨 How to define a piece

    of UI? Appdevcon - @marcoGomier
  52. 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
  53. Component Widget composable function struct 👨🎨 How to define a

    piece of UI? Appdevcon - @marcoGomier
  54. 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
  55. Component Widget composable function struct 👨🎨 How to define a

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

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

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

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

    return a + b } 👍
  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. Appdevcon - @marcoGomier 👍 fun sum(a: Int, b: Int): Int

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

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

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

  65. None
  66. Appdevcon - @marcoGomier fun checkComponent( isChecked: Boolean, onSelectionChange: () ->

    Unit, ): MaterialSwitch { return MaterialSwitch( selected = isChecked, onSelectionChange = { onSelectionChange() }, ) }
  67. fun checkComponent( isChecked: Boolean, onSelectionChange: () -> Unit, ): MaterialSwitch

    { return MaterialSwitch ( selected = isChecked, onSelectionChange = { onSelectionChange() }, ) } fun checkComponent( isChecked: Boolean, onSelectionChange: () -> Unit, ): MaterialSwitch { return MaterialSwitch ( selected = isChecked, onSelectionChange = { onSelectionChange() }, ) } SwitchComponent isChecked State Holder Appdevcon - @marcoGomier
  68. SwitchComponent State Holder onSelectionChange Appdevcon - @marcoGomier fun checkComponent( isChecked:

    Boolean, onSelectionChange: () -> Unit, ): MaterialSwitch { return MaterialSwitch ( selected = isChecked, onSelectionChange = { onSelectionChange() }, ) }
  69. SwitchComponent isChecked fun checkComponent( isChecked: Boolean, onSelectionChange: () -> Unit,

    ): MaterialSwitch { return MaterialSwitch ( selected = isChecked, onSelectionChange = { onSelectionChange() }, ) }
  70. Appdevcon - @marcoGomier State goes down Events goes up Unidirectional

    Data Flow
  71. 🧩 Appdevcon - @marcoGomier

  72. 🧩 Composition Appdevcon - @marcoGomier

  73. Inheritance 🧩 Composition over Appdevcon - @marcoGomier

  74. Appdevcon - @marcoGomier 🧩 Inheritance fun Container() : View() {

    .. } fun NewsCard() : View() { .. } fun NewsCardContainer() : ?? { .. }
  75. Appdevcon - @marcoGomier 🧩 Composition fun NewsCardContainer() { NewsCard {

    Container { ... } } } fun Container() { .. } fun NewsCard() { .. }
  76. Imperative vs Declarative Appdevcon - @marcoGomier

  77. 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() } } ->
  78. 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
  79. Appdevcon - @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() } }
  80. Appdevcon - @marcoGomier https://github.com/prof18/imperative-vs-declarative

  81. Conclusions Appdevcon - @marcoGomier

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

    HOW • Less code to write → Fewer bugs • Totally new paradigm, it requires time to get used to
  83. 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 • https: // developer.android.com/jetpack/compose/state
  84. Thank you! > Twitter: @marcoGomier 
 > Github: prof18 


    > Website: marcogomiero.com 👨💻 Senior Android Engineer @ TIER 
 Google Developer Expert for Kotlin Appdevcon - @marcoGomier