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.

Marco Gomiero

October 14, 2020
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

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

    S.T.A.Y. Home #4 - @marcoGomier > Twitter: @marcoGomier > Github: prof18 > Website: marcogomiero.com
  2. 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
  3. How to built it now? S.T.A.Y. Home #4 - @marcoGomier

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

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

    weak var loader: UIActivityIndicatorView! S.T.A.Y. Home #4 - @marcoGomier
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. “ a programming paradigm that uses statements that change a

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

    update step? S.T.A.Y. Home #4 - @marcoGomier
  15. “ what the program should accomplish without specifying how the

    program should achieve the result. ” S.T.A.Y. Home #4 - @marcoGomier - Wikipedia
  16. 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
  17. 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]
  18. S.T.A.Y. Home #4 - @marcoGomier Changes are handled by the

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

    1. Creates an abstract representation of the UI and renders it
  20. 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
  21. 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]
  22. 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!' } } } }
  23. 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
  24. 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
  25. 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)
  26. 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
  27. Jetpack Compose S.T.A.Y. Home #4 - @marcoGomier • Reuse the

    node unless it changed youtube.com/watch?v=6BRlI5zfCCk
  28. 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; } }
  29. 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: ""... , ), ); } }
  30. 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) ""... } } }
  31. 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) ""... } } } }
  32. 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) !
  33. 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 }
  34. 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 }
  35. Pure Function S.T.A.Y. Home #4 - @marcoGomier • Returns the

    same value given the same input • Has no side effects
  36. S.T.A.Y. Home #4 - @marcoGomier fun checkComponent(isChecked: Boolean): MaterialSwitch {

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

    MaterialSwitch { return MaterialSwitch ( selected = isChecked, onSelectionChange = { selection "-> settings.update(selection) } ) }
  40. S.T.A.Y. Home #4 - @marcoGomier Inheritance fun Container() : View()

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

    { Container { ""... } } } fun Container() { ".. } fun NewsCard() { ".. }
  42. 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() } }
  43. 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
  44. 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() } }
  45. 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
  46. 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
  47. Thank you! > Twitter: @marcoGomier > Github: prof18 > Website:

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