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

    View Slide

  2. Hacker News Client
    Loading State
    Appdevcon - @marcoGomier

    View Slide

  3. Hacker News Client
    Error State
    Loading State
    Appdevcon - @marcoGomier

    View Slide

  4. Hacker News Client
    Success State
    Loading State Error State
    Appdevcon - @marcoGomier

    View Slide

  5. 👷 How to built it 👑? 🚧
    Appdevcon - @marcoGomier

    View Slide

  6. 👷 How to built it 👑? 🚧
    1. Define the view
    Appdevcon - @marcoGomier

    View Slide

  7. XML or Code
    1. Define the view


    ... />



    android:id="@+id/progress_bar"


    android:layout_width="wrap_content"


    android:layout_height="wrap_content"


    ... />




    android:id="@+id/error_message"


    android:layout_width="wrap_content"


    android:layout_height="wrap_content"


    android:visibility="gone"


    ... />




    android:id="@+id/error_button"


    android:layout_width="wrap_content"


    android:layout_height="wrap_content"


    android:text="Refresh"


    android:visibility="gone"


    ... />



    android:id="@+id/recycler_view"


    android:layout_width="match_parent"


    android:layout_height="match_parent"


    android:visibility="gone"


    ... />


    < /
    androidx.constraintlayout.widget.ConstraintLayout>


    Appdevcon - @marcoGomier

    View Slide

  8. Storyboard or Code
    1. Define the view
    Appdevcon - @marcoGomier

    View Slide

  9. 👷 How to built it 👑? 🚧
    1. Define the view
    2. Retrieve the views instances
    Appdevcon - @marcoGomier

    View Slide

  10. 1. Define the view
    2. Retrieve the views instances
    val progressBar = findViewById(R.id.progress_bar)


    Appdevcon - @marcoGomier

    View Slide

  11. 1. Define the view
    2. Retrieve the views instances
    @IBOutlet weak var loader: UIActivityIndicatorView!


    Appdevcon - @marcoGomier

    View Slide

  12. 👷 How to built it 👑? 🚧
    1. Define the view
    2. Retrieve the views instances
    3. Add some code to handle a list
    Appdevcon - @marcoGomier

    View Slide

  13. 1. Define the view
    2. Retrieve the views instances
    3. Add some code to handle a list
    RecyclerView.Adapter
    class NewsAdapter(var items: List = listOf()) :


    RecyclerView.Adapter() {


    ...

    }


    Appdevcon - @marcoGomier

    View Slide

  14. 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

    View Slide

  15. 👷 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

    View Slide

  16. 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

    View Slide

  17. 👑 Imperative
    • Lots of boilerplate and boring code


    • Errors and bugs prone


    • Not a single source of truth


    • Hard to maintain
    Appdevcon - @marcoGomier

    View Slide

  18. 👑 Imperative
    Appdevcon - @marcoGomier

    View Slide

  19. “ a programming paradigm
    that uses statements that
    change a program's state ”
    - Wikipedia
    Appdevcon - @marcoGomier

    View Slide

  20. state A
    Appdevcon - @marcoGomier

    View Slide

  21. state A state B

    View Slide

  22. state A
    🧙
    Appdevcon - @marcoGomier

    View Slide

  23. state B
    🧙
    Appdevcon - @marcoGomier

    View Slide

  24. 📣 Declarative
    Appdevcon - @marcoGomier

    View Slide

  25. “ what the program should
    accomplish without specifying how
    the program should achieve the
    result. ”
    - Wikipedia
    Appdevcon - @marcoGomier

    View Slide

  26. 🧐
    Appdevcon - @marcoGomier

    View Slide

  27. UI = f(state)
    Appdevcon - @marcoGomier

    View Slide

  28. 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

    View Slide

  29. 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

    View Slide

  30. Changes are handled by the magician system


    not by the developer
    🧙
    Appdevcon - @marcoGomier

    View Slide

  31. How the magician system works
    1. Creates an abstract representation of the UI and renders it


    Appdevcon - @marcoGomier

    View Slide

  32. 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

    View Slide

  33. 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

    View Slide

  34. 🤔 How it works in details?
    Appdevcon - @marcoGomier

    View Slide

  35. 🤷 It depends!
    Appdevcon - @marcoGomier

    View Slide

  36. React [Native]
    • Virtual DOM as representation of the UI








    OK!



    b>



    button>


    {


    type: 'button',


    props: {


    className: 'button button-blue',


    children: {


    type: 'b',


    props: {


    children: 'OK!'


    }


    }


    }


    }


    Appdevcon - @marcoGomier

    View Slide

  37. React [Native]
    • Virtual DOM as representation of the UI


    • Diff comparison using reconciliation, an O(n) heuristic
    algorithm
    Appdevcon - @marcoGomier

    View Slide

  38. 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

    View Slide

  39. Flutter
    • Element Tree as representation of the UI
    Appdevcon - @marcoGomier

    View Slide

  40. 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

    View Slide

  41. 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

    View Slide

  42. Jetpack Compose
    • Slot table as representation of the UI
    Appdevcon - @marcoGomier

    View Slide

  43. Jetpack Compose
    • Every node is cached with a key
    Appdevcon - @marcoGomier

    View Slide

  44. Jetpack Compose
    • Reuse the node unless it changed
    Appdevcon - @marcoGomier

    View Slide

  45. Jetpack Compose
    Appdevcon - @marcoGomier
    • Reuse the node unless it changed
    youtube.com/watch?v=6BRlI5zfCCk

    View Slide

  46. 👨🎨 How to define a piece of UI?
    Appdevcon - @marcoGomier

    View Slide

  47. Component
    👨🎨 How to define a piece of UI?
    Appdevcon - @marcoGomier

    View Slide

  48. Component
    Component
    export default class NewsCard extends React.Component {


    constructor(props) {


    super(props);


    }


    render() {


    const { news } = this.props;


    if (news) {


    return (





    . .
    >


    ..
    >


    ..
    >



    TouchableOpacity>



    View>


    );


    }


    return null;


    }


    }


    Appdevcon - @marcoGomier

    View Slide

  49. Component
    👨🎨 How to define a piece of UI?
    Widget
    Appdevcon - @marcoGomier

    View Slide

  50. 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

    View Slide

  51. Component
    Widget
    composable function
    👨🎨 How to define a piece of UI?
    Appdevcon - @marcoGomier

    View Slide

  52. 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

    View Slide

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

    View Slide

  54. 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

    View Slide

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

    View Slide

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

    View Slide

  57. Component
    Widget
    composable function
    struct
    👨🎨 How to define a piece of UI?
    With a function (of the state)
    !
    Appdevcon - @marcoGomier

    View Slide

  58. Pure Function
    Appdevcon - @marcoGomier

    View Slide

  59. Appdevcon - @marcoGomier
    fun sum(a: Int, b: Int): Int {


    return a + b


    }


    👍

    View Slide

  60. Appdevcon - @marcoGomier
    👍 fun sum(a: Int, b: Int): Int {


    return a + b


    }


    fun addExpense(account: BackAccount, amount: Int) {


    account.balance = account.balance - amount


    }


    View Slide

  61. Appdevcon - @marcoGomier
    👍 fun sum(a: Int, b: Int): Int {


    return a + b


    }


    fun addExpense(account: BackAccount, amount: Int) {


    account.balance = account.balance - amount


    }


    👎

    View Slide

  62. Pure Function
    Appdevcon - @marcoGomier
    • Returns the same value given the same input


    • Has no side effects


    View Slide

  63. Appdevcon - @marcoGomier
    Take some inputs and return a piece of UI

    View Slide

  64. UI = f(state)
    Appdevcon - @marcoGomier

    View Slide

  65. View Slide

  66. Appdevcon - @marcoGomier
    fun checkComponent(


    isChecked: Boolean,


    onSelectionChange: ()
    ->
    Unit,


    ): MaterialSwitch {


    return MaterialSwitch(


    selected = isChecked,


    onSelectionChange = { onSelectionChange() },


    )


    }

    View Slide

  67. 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

    View Slide

  68. SwitchComponent
    State Holder
    onSelectionChange
    Appdevcon - @marcoGomier
    fun checkComponent(


    isChecked: Boolean,


    onSelectionChange: ()
    ->
    Unit,


    ): MaterialSwitch {


    return MaterialSwitch (


    selected = isChecked,


    onSelectionChange = { onSelectionChange() },


    )


    }

    View Slide

  69. SwitchComponent
    isChecked
    fun checkComponent(


    isChecked: Boolean,


    onSelectionChange: ()
    ->
    Unit,


    ): MaterialSwitch {


    return MaterialSwitch (


    selected = isChecked,


    onSelectionChange = { onSelectionChange() },


    )


    }

    View Slide

  70. Appdevcon - @marcoGomier
    State goes down
    Events goes up
    Unidirectional Data Flow

    View Slide

  71. 🧩
    Appdevcon - @marcoGomier

    View Slide

  72. 🧩 Composition
    Appdevcon - @marcoGomier

    View Slide

  73. Inheritance
    🧩 Composition over
    Appdevcon - @marcoGomier

    View Slide

  74. Appdevcon - @marcoGomier
    🧩 Inheritance
    fun Container() : View() {
    ..
    }


    fun NewsCard() : View() {
    ..
    }


    fun NewsCardContainer() :
    ??
    {
    ..
    }


    View Slide

  75. Appdevcon - @marcoGomier
    🧩 Composition
    fun NewsCardContainer() {


    NewsCard {


    Container {


    ...

    }


    }


    }


    fun Container() {
    ..
    }


    fun NewsCard() {
    ..
    }


    View Slide

  76. Imperative vs Declarative
    Appdevcon - @marcoGomier

    View Slide

  77. 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()


    }


    }

    ->

    View Slide

  78. 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

    View Slide

  79. 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()


    }


    }


    View Slide

  80. Appdevcon - @marcoGomier https://github.com/prof18/imperative-vs-declarative

    View Slide

  81. Conclusions
    Appdevcon - @marcoGomier

    View Slide

  82. 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

    View Slide

  83. 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


    View Slide

  84. Thank you!
    > Twitter: @marcoGomier

    > Github: prof18

    > Website: marcogomiero.com
    👨💻 Senior Android Engineer @ TIER

    Google Developer Expert for Kotlin
    Appdevcon - @marcoGomier

    View Slide