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

Imperative is dead, long live Declarative! - Ap...

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

November 12, 2020
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

  1. Marco Gomiero ! Tech Lead @ Uniwhere " # $

    Co-Lead @ GDG Venezia Appdevcon - @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> Appdevcon - @marcoGomier
  3. Appdevcon - @marcoGomier % How to built it now? &

    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) Appdevcon - @marcoGomier
  5. 1. Define the view 2. Retrieve the views instances @IBOutlet

    weak var loader: UIActivityIndicatorView! Appdevcon - @marcoGomier
  6. Appdevcon - @marcoGomier % How to built it now? &

    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>() { ""# } Appdevcon - @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 Appdevcon - @marcoGomier
  9. Appdevcon - @marcoGomier % How to built it now? &

    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 ""# } } Appdevcon - @marcoGomier
  11. ' Imperative Appdevcon - @marcoGomier • Lots of boilerplate and

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

    that change a program's state ” - Wikipedia
  13. Appdevcon - @marcoGomier ( What if I can declare the

    views and forget the update step?
  14. Appdevcon - @marcoGomier “ what the program should accomplish without

    specifying how the program should achieve the result. ” - Wikipedia
  15. Appdevcon - @marcoGomier state • Describes the UI right now,

    i.e. what to show • Independent of the previous state[s] • If changes, the UI is redrawn
  16. How the magician system works Appdevcon - @marcoGomier 1. Creates

    an abstract representation of the UI and renders it
  17. How the magician system works Appdevcon - @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] Appdevcon - @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] Appdevcon - @marcoGomier • Virtual DOM as representation

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

    it changed youtube.com/watch?v=6BRlI5zfCCk
  24. 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
  25. 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
  26. 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
  27. 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
  28. Appdevcon - @marcoGomier Component Widget composable function struct - How

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

    { return a + b } fun addExpense(account: BackAccount, amount: Int) { account.balance = account.balance - amount }
  30. Appdevcon - @marcoGomier . fun sum(a: Int, b: Int): Int

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

    given the same input • Has no side effects
  32. Appdevcon - @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) } ) } SwitchComponent isChecked State Holder Appdevcon - @marcoGomier
  34. fun checkComponent(isChecked: Boolean): MaterialSwitch { return MaterialSwitch ( selected =

    isChecked, onSelectionChange = { selection "& settings.update(selection) } ) } SwitchComponent State Holder onSelectionChange Appdevcon - @marcoGomier
  35. SwitchComponent isChecked fun checkComponent(isChecked: Boolean): MaterialSwitch { return MaterialSwitch (

    selected = isChecked, onSelectionChange = { selection "& settings.update(selection) } ) } Appdevcon - @marcoGomier
  36. Appdevcon - @marcoGomier 0 Inheritance fun Container() : View() {

    "' } fun NewsCard() : View() { "' } fun NewsCardContainer() : "( { "' }
  37. Appdevcon - @marcoGomier 0 Composition fun NewsCardContainer() { NewsCard {

    Container { ""# } } } fun Container() { "' } fun NewsCard() { "' }
  38. 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() } }
  39. 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
  40. Appdevcon - @marcoGomier when (appState")newsState) { is 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. Appdevcon - @marcoGomier Conclusions • 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