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.

Marco Gomiero

April 29, 2021
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

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

    
 🍻 Co-Lead @ GDG Venezia .droidcon ONLINE - @marcoGomier > Twitter: @marcoGomier 
 > Github: prof18 
 > Blog: marcogomiero.com
  2. 👷 How to built it 👑? 🚧 .droidcon ONLINE -

    @marcoGomier 1. Define the view
  3. 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
  4. 👷 How to built it 👑? 🚧 .droidcon ONLINE -

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

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

    @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>() { ... } .droidcon ONLINE - @marcoGomier
  8. 👷 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
  9. 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
  10. 👑 Imperative .droidcon ONLINE - @marcoGomier • Lots of boilerplate

    and boring code • Errors and bugs prone • Not a single source of truth • Hard to maintain
  11. “ a programming paradigm that uses statements that change a

    program's state ” .droidcon ONLINE - @marcoGomier - Wikipedia
  12. “ what the program should accomplish without specifying how the

    program should achieve the result. ” .droidcon ONLINE - @marcoGomier - Wikipedia
  13. .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
  14. .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]
  15. How the magician system works .droidcon ONLINE - @marcoGomier 1.

    Creates an abstract representation of the UI and renders it
  16. 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
  17. 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]
  18. 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!' } } } }
  19. React [Native] .droidcon ONLINE - @marcoGomier • Virtual DOM as

    representation of the UI • Diff comparison using reconciliation, an O(n) heuristic algorithm
  20. 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
  21. 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)
  22. 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
  23. Jetpack Compose .droidcon ONLINE - @marcoGomier • Reuse the node

    unless it changed youtube.com/watch?v=6BRlI5zfCCk
  24. 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; } }
  25. .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: ... , ), ); } }
  26. .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) .. . } } }
  27. .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) .. . } } } }
  28. .droidcon ONLINE - @marcoGomier Component Widget composable function struct 👨🎨

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

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

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

    Int { return a + b } fun addExpense(account: BackAccount, amount: Int) { account.balance = account.balance - amount } 👎
  32. .droidcon ONLINE - @marcoGomier fun checkComponent(isChecked: Boolean): MaterialSwitch { return

    MaterialSwitch ( selected = isChecked, onSelectionChange = { selection -> settings.update(selection) } ) }
  33. 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
  34. fun checkComponent(isChecked: Boolean): MaterialSwitch { return MaterialSwitch ( selected =

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

    { return MaterialSwitch ( selected = isChecked, onSelectionChange = { selection -> settings.update(selection) } ) }
  36. .droidcon ONLINE - @marcoGomier 🧩 Inheritance fun Container() : View()

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

    { Container { ... } } } fun Container() { .. } fun NewsCard() { .. }
  38. .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() } } ->
  39. .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
  40. .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() } }
  41. 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
  42. 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
  43. Thank you! .droidcon ONLINE - @marcoGomier > Twitter: @marcoGomier 


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