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

Imperative is dead, long live Declarative! - .droidcon ONLINE - Webinar series

Imperative is dead, long live Declarative! - .droidcon ONLINE - Webinar series

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

April 29, 2021
Tweet

Transcript

  1. Imperative is dead, long live Declarative .droidcon ONLINE - Marco

    Gomiero
  2. Marco Gomiero 👨💻 Tech Lead @ revelop.app 🇮🇹 🇩🇪 🇺🇸

    
 🍻 Co-Lead @ GDG Venezia .droidcon ONLINE - @marcoGomier > Twitter: @marcoGomier 
 > Github: prof18 
 > Blog: marcogomiero.com
  3. Hacker News Client Loading State .droidcon ONLINE - @marcoGomier

  4. Hacker News Client Error State .droidcon ONLINE - @marcoGomier Loading

    State
  5. Hacker News Client Success State .droidcon ONLINE - @marcoGomier Loading

    State Error State
  6. 👷 How to built it 👑? 🚧 .droidcon ONLINE -

    @marcoGomier
  7. 👷 How to built it 👑? 🚧 .droidcon ONLINE -

    @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> .droidcon ONLINE - @marcoGomier
  9. 👷 How to built it 👑? 🚧 .droidcon ONLINE -

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

    progressBar = findViewById<ProgressBar>(R.id.progress_bar) .droidcon ONLINE - @marcoGomier
  11. 👷 How to built it 👑? 🚧 .droidcon ONLINE -

    @marcoGomier 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>() { ... } .droidcon ONLINE - @marcoGomier
  13. 👷 How to built it 👑? 🚧 .droidcon ONLINE -

    @marcoGomier 1. Define the view 2. Retrieve the views instances 3. Add some code to handle a list 4. Add some logic
  14. 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 ... } } .droidcon ONLINE - @marcoGomier
  15. 👑 Imperative .droidcon ONLINE - @marcoGomier • Lots of boilerplate

    and boring code • Errors and bugs prone • Not a single source of truth • Hard to maintain
  16. 👑 Imperative .droidcon ONLINE - @marcoGomier

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

    program's state ” .droidcon ONLINE - @marcoGomier - Wikipedia
  18. state A .droidcon ONLINE - @marcoGomier

  19. state A state B

  20. state A .droidcon ONLINE - @marcoGomier 🧙

  21. state B .droidcon ONLINE - @marcoGomier 🧙

  22. 📣 Declarative .droidcon ONLINE - @marcoGomier

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

    program should achieve the result. ” .droidcon ONLINE - @marcoGomier - Wikipedia
  24. 🧐 .droidcon ONLINE - @marcoGomier

  25. UI = f( .droidcon ONLINE - @marcoGomier state)

  26. .droidcon ONLINE - @marcoGomier state • Describes the UI right

    now, i.e. what to show • Independent of the previous state[s] • If changes, the UI is redrawn
  27. .droidcon ONLINE - @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]
  28. .droidcon ONLINE - @marcoGomier Changes are handled by the magician

    system not by the developer 🧙
  29. How the magician system works .droidcon ONLINE - @marcoGomier 1.

    Creates an abstract representation of the UI and renders it
  30. How the magician system works .droidcon ONLINE - @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
  31. How the magician system works .droidcon ONLINE - @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]
  32. 🤔 How it works in details? .droidcon ONLINE - @marcoGomier

  33. 🤷 It depends! .droidcon ONLINE - @marcoGomier

  34. React [Native] .droidcon ONLINE - @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!' } } } }
  35. React [Native] .droidcon ONLINE - @marcoGomier • Virtual DOM as

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

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

    of the UI
  38. Flutter .droidcon ONLINE - @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)
  39. Flutter .droidcon ONLINE - @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
  40. Jetpack Compose .droidcon ONLINE - @marcoGomier • Slot table as

    representation of the UI
  41. Jetpack Compose .droidcon ONLINE - @marcoGomier • Every node is

    cached with a key
  42. Jetpack Compose .droidcon ONLINE - @marcoGomier • Reuse the node

    unless it changed
  43. Jetpack Compose .droidcon ONLINE - @marcoGomier • Reuse the node

    unless it changed youtube.com/watch?v=6BRlI5zfCCk
  44. .droidcon ONLINE - @marcoGomier 👨🎨 How to define a piece

    of UI?
  45. .droidcon ONLINE - @marcoGomier Component 👨🎨 How to define a

    piece of UI?
  46. Component .droidcon ONLINE - @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; } }
  47. .droidcon ONLINE - @marcoGomier Component 👨🎨 How to define a

    piece of UI? Widget
  48. .droidcon ONLINE - @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: ... , ), ); } }
  49. .droidcon ONLINE - @marcoGomier Component Widget composable function 👨🎨 How

    to define a piece of UI?
  50. .droidcon ONLINE - @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) .. . } } }
  51. .droidcon ONLINE - @marcoGomier Component Widget composable function struct 👨🎨

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

    How to define a piece of UI?
  54. .droidcon ONLINE - @marcoGomier Component Widget composable function struct 👨🎨

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

    How to define a piece of UI? With a function (of the state) !
  56. .droidcon ONLINE - @marcoGomier Pure Function

  57. Pure Function .droidcon ONLINE - @marcoGomier • Returns the same

    value given the same input • Has no side effects
  58. .droidcon ONLINE - @marcoGomier fun sum(a: Int, b: Int): Int

    { return a + b } 👍
  59. .droidcon ONLINE - @marcoGomier 👍 fun sum(a: Int, b: Int):

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

    Int { return a + b } fun addExpense(account: BackAccount, amount: Int) { account.balance = account.balance - amount } 👎
  61. .droidcon ONLINE - @marcoGomier Take some inputs and return a

    piece of UI
  62. UI = f(state) .droidcon ONLINE - @marcoGomier

  63. None
  64. .droidcon ONLINE - @marcoGomier fun checkComponent(isChecked: Boolean): MaterialSwitch { return

    MaterialSwitch ( selected = isChecked, onSelectionChange = { selection -> settings.update(selection) } ) }
  65. 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) } ) } .droidcon ONLINE - @marcoGomier SwitchComponent isChecked State Holder
  66. fun checkComponent(isChecked: Boolean): MaterialSwitch { return MaterialSwitch ( selected =

    isChecked, onSelectionChange = { selection -> settings.update(selection) } ) } .droidcon ONLINE - @marcoGomier SwitchComponent State Holder onSelectionChange
  67. .droidcon ONLINE - @marcoGomier SwitchComponent isChecked fun checkComponent(isChecked: Boolean): MaterialSwitch

    { return MaterialSwitch ( selected = isChecked, onSelectionChange = { selection -> settings.update(selection) } ) }
  68. .droidcon ONLINE - @marcoGomier State goes down Events goes up

    Unidirectional Data Flow
  69. .droidcon ONLINE - @marcoGomier 🧩

  70. .droidcon ONLINE - @marcoGomier 🧩 Composition

  71. Inheritance .droidcon ONLINE - @marcoGomier 🧩 Composition over

  72. .droidcon ONLINE - @marcoGomier 🧩 Inheritance fun Container() : View()

    { .. } fun NewsCard() : View() { .. } fun NewsCardContainer() : ?? { .. }
  73. .droidcon ONLINE - @marcoGomier 🧩 Composition fun NewsCardContainer() { NewsCard

    { Container { ... } } } fun Container() { .. } fun NewsCard() { .. }
  74. Imperative vs Declarative .droidcon ONLINE - @marcoGomier

  75. .droidcon ONLINE - @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() } } ->
  76. .droidcon ONLINE - @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
  77. .droidcon ONLINE - @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() } }
  78. .droidcon ONLINE - @marcoGomier https://github.com/prof18/imperative-vs-declarative

  79. Conclusions .droidcon ONLINE - @marcoGomier

  80. Conclusions .droidcon ONLINE - @marcoGomier • Describe WHAT to see

    NOT HOW • Less code to write → Fewer bugs • Totally new paradigm, it requires time to get used to
  81. 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
  82. Thank you! .droidcon ONLINE - @marcoGomier > Twitter: @marcoGomier 


    > Github: prof18 
 > Blog: marcogomiero.com revelop.app