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.

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. 👷 How to built it 👑? 🚧 1. Define the

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

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

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

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

    view 2. Retrieve the views instances 3. Add some code to handle a list Appdevcon - @marcoGomier
  8. 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
  9. 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
  10. 👷 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
  11. 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
  12. 👑 Imperative • Lots of boilerplate and boring code •

    Errors and bugs prone • Not a single source of truth • Hard to maintain Appdevcon - @marcoGomier
  13. “ a programming paradigm that uses statements that change a

    program's state ” - Wikipedia Appdevcon - @marcoGomier
  14. “ what the program should accomplish without specifying how the

    program should achieve the result. ” - Wikipedia Appdevcon - @marcoGomier
  15. 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
  16. 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
  17. Changes are handled by the magician system not by the

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

    of the UI and renders it Appdevcon - @marcoGomier
  19. 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
  20. 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
  21. 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
  22. React [Native] • Virtual DOM as representation of the UI

    • Diff comparison using reconciliation, an O(n) heuristic algorithm Appdevcon - @marcoGomier
  23. 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
  24. 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
  25. 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
  26. Jetpack Compose Appdevcon - @marcoGomier • Reuse the node unless

    it changed youtube.com/watch?v=6BRlI5zfCCk
  27. 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
  28. 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
  29. 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
  30. 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
  31. Component Widget composable function struct 👨🎨 How to define a

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

    piece of UI? With a function (of the state) ! Appdevcon - @marcoGomier
  33. Appdevcon - @marcoGomier 👍 fun sum(a: Int, b: Int): Int

    { return a + b } fun addExpense(account: BackAccount, amount: Int) { account.balance = account.balance - amount }
  34. Appdevcon - @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 Appdevcon - @marcoGomier • Returns the same value

    given the same input • Has no side effects
  36. Appdevcon - @marcoGomier fun checkComponent( isChecked: Boolean, onSelectionChange: () ->

    Unit, ): MaterialSwitch { return MaterialSwitch( selected = isChecked, onSelectionChange = { onSelectionChange() }, ) }
  37. 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
  38. SwitchComponent State Holder onSelectionChange Appdevcon - @marcoGomier fun checkComponent( isChecked:

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

    ): MaterialSwitch { return MaterialSwitch ( selected = isChecked, onSelectionChange = { onSelectionChange() }, ) }
  40. Appdevcon - @marcoGomier 🧩 Inheritance fun Container() : View() {

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

    Container { ... } } } fun Container() { .. } fun NewsCard() { .. }
  42. 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() } } ->
  43. 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
  44. 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() } }
  45. 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
  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 • https: // developer.android.com/jetpack/compose/state
  47. Thank you! > Twitter: @marcoGomier 
 > Github: prof18 


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