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

A Composable New World

cmota
August 11, 2021

A Composable New World

As our Twitter streams become flooded with the release of Jetpack Compose 1.0, it's time to jump into the UI declarative world and reform the XML and all of the `findViewById` calls that exist scattered throughout the code.

Join me in this talk to see the first steps into this composable new world and build your first app (with Compose)!

cmota

August 11, 2021
Tweet

More Decks by cmota

Other Decks in Education

Transcript

  1. 👨💻 Android GDE 🗣 Founder @GDGCoimbra and co-founder @Kotlin_Knights ✍

    Author @rwenderlich 🎙 Podcaster wannabe 🗺 Loves travel, photography and running @cafonsomota
  2. Android * Declarative UI Patterns (I/O’19) - https://www.youtube.com/watch?v=VsStyq4Lzxo 2008 2009

    2010 1.0 A lot of Android version in between Android Studio 2013 2014 2015 2016 2017 2018 ART RecyclerView Constraint Layout Arch Components Kotlin
  3. 1. Git Clone • github.com/cmota/unsplash-compose 2. Download Android Studio Arctic

    Fox* • developer.android.com/studio 3. Create your Unsplash API account • unsplash.com/developers Materials * ⚠ There’s a Specific version for Mac m1
  4. What’s Compose? verb 1. 
 write or create (a work

    of art, especially music or poetry). "he composed the First Violin Sonata four years earlier" Definitions from Oxford Languages
  5. Jetpack Compose MODERN FRAMEWORK Declarative UI Less code Smaller APK’s

    Kotlin Accelerate development Intuitive Open-source Unbundled from OS
  6. Jetpack Compose MODERN FRAMEWORK Declarative UI Less code Kotlin Accelerate

    development Intuitive Open-source Smaller APK’s Unbundled from OS
  7. Imperative UI Exercise: Implementing a List This is item 1

    This is item 2 This is item 3 This is item 4 This is item 5 This is item 6
  8. Imperative UI Exercise: Implementing a List 1. Create an activity

    2. Create the correspondent xml activity_main.xml
  9. Imperative UI Exercise: Implementing a List 1. Create an activity

    2. Create the correspondent xml 3. Create a fragment MainFragment.kt
  10. Imperative UI Exercise: Implementing a List 1. Create an activity

    2. Create the correspondent xml 3. Create a fragment 4. Create the correspondent xml fragment_main.xml
  11. Imperative UI Exercise: Implementing a List 1. Create an activity

    2. Create the correspondent xml 3. Create a fragment 4. Create the correspondent xml 5. Create a recycler view RecyclerView
  12. Imperative UI Exercise: Implementing a List 1. Create an activity

    2. Create the correspondent xml 3. Create a fragment 4. Create the correspondent xml 5. Create a recycler view 6. Create the adapter Adapter
  13. Imperative UI Exercise: Implementing a List 1. Create an activity

    2. Create the correspondent xml 3. Create a fragment 4. Create the correspondent xml 5. Create a recycler view 6. Create the adapter 7. Create the correspondent xml for the items item_adapter.xml
  14. Imperative UI Exercise: Implementing a List item_adapter.xml files created 5

    (It can be more if we want to add further customization) Lines of code written +200 (Between declarations and xml attributes)
  15. Imperative UI Exercise: Implementing a List item_adapter.xml • Time consuming

    • Error prone • Several files created • Increasing APK size • Coupled components
  16. How to change the access and change the state of

    a view Imperative UI Exercise: Changing the state of a view Hello world. A brief history on or
  17. Imperative UI Exercise: Changing the state of a view Hello

    world. android:Id = “@+id/tv_greeting” Text TextView greetings = (TextView) findViewById(R.id.tv_greeting) greetings.text = “Hello world.”
  18. Imperative UI Exercise: Changing the state of a view Hello

    world. android:Id = “@+id/tv_greeting” Text @BindView(R.id.tv_greeting) TextView greetings; greetings.text = “Hello world.” github.com/JakeWharton/butterknife
  19. Imperative UI Exercise: Changing the state of a view Hello

    world. android:Id = “@+id/tv_greeting” Text TextView greetings = (TextView) findViewById(R.id.tv_greeting) greetings.text = “Hello world.”
  20. Imperative UI Exercise: Changing the state of a view Hello

    world. android:Id = “@+id/tv_greeting” Text TextView greetings = findViewById(R.id.tv_greeting) greetings.text = “Hello world.”
  21. Imperative UI Exercise: Changing the state of a view Hello

    world. android:Id = “@+id/tv_greeting” Text import kotlinx.android.synthetic.activity_main.* tv_greeting.text = “Hello world.”
  22. Imperative UI Exercise: Changing the state of a view Hello

    world. android:Id = “@+id/tv_greeting” Text import kotlinx.android.synthetic.activity_main.* tv_greeting.text = “Hello world.” DEPRECATED
  23. Imperative UI Exercise: Changing the state of a view Hello

    world. android:Id = “@+id/tv_greeting” Text buildFeatures.viewBinding true private lateinit var binding: FragmentMainBinding override fun onCreateView( infltr: LayoutInflater, container: ViewGroup?, state: Bundle?): View { binding = FragmentMainBinding.inflate(infltr, container, false) return binding.root } binding.tvGreeting.text = “Hello world.” build.gradle
  24. Imperative UI With 30407 lines of code and several classes

    that extend it - Difficult to scale - Hard to maintain - Every change might reflect on a lot of classes - Bundled to the OS and OEM’s implementation of these components
  25. Goals Unbundle from platform releases Faster to implement new designs

    (views/components) Clarify state ownership and event handling * Adapted from declarative UI patterns (Google I/O’19) - https://www.youtube.com/watch?v=VsStyq4Lzxo&t
  26. Shifting paradigms Imperative UI Ui as a function | Kotlin

    code XML java | kotlin code Layouts Attrs Styles … Declarative ui
  27. Shifting paradigms medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050 fun updateCount(count: Int) { if (count >

    0 && !hasBadge()) { addBadge() } else if (count == 0 && hasBadge()) { removeBadge() } if (count > 99 && !hasFire()) { addFire() setBadgeText("99+") } else if (count <= 99 && hasFire()) { removeFire() } if (count > 0 && !hasPaper()) { addPaper() } else if (count == 0 && hasPaper()) { removePaper() } if (count <= 99) { setBadgeText("$count") } } Imperative UI
  28. Shifting paradigms medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050 fun updateCount(count: Int) { if (count >

    0 && !hasBadge()) { addBadge() } else if (count == 0 && hasBadge()) { removeBadge() } if (count > 99 && !hasFire()) { addFire() setBadgeText("99+") } else if (count <= 99 && hasFire()) { removeFire() } if (count > 0 && !hasPaper()) { addPaper() } else if (count == 0 && hasPaper()) { removePaper() } if (count <= 99) { setBadgeText("$count") } } Imperative UI Showing/hiding badge
  29. Shifting paradigms medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050 fun updateCount(count: Int) { if (count >

    0 && !hasBadge()) { addBadge() } else if (count == 0 && hasBadge()) { removeBadge() } if (count > 99 && !hasFire()) { addFire() setBadgeText("99+") } else if (count <= 99 && hasFire()) { removeFire() } if (count > 0 && !hasPaper()) { addPaper() } else if (count == 0 && hasPaper()) { removePaper() } if (count <= 99) { setBadgeText("$count") } } Imperative UI Showing/hiding badge
  30. Shifting paradigms medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050 fun updateCount(count: Int) { if (count >

    0 && !hasBadge()) { addBadge() } else if (count == 0 && hasBadge()) { removeBadge() } if (count > 99 && !hasFire()) { addFire() setBadgeText("99+") } else if (count <= 99 && hasFire()) { removeFire() } if (count > 0 && !hasPaper()) { addPaper() } else if (count == 0 && hasPaper()) { removePaper() } if (count <= 99) { setBadgeText("$count") } } Imperative UI Showing/hiding Fire
  31. Shifting paradigms medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050 fun updateCount(count: Int) { if (count >

    0 && !hasBadge()) { addBadge() } else if (count == 0 && hasBadge()) { removeBadge() } if (count > 99 && !hasFire()) { addFire() setBadgeText("99+") } else if (count <= 99 && hasFire()) { removeFire() } if (count > 0 && !hasPaper()) { addPaper() } else if (count == 0 && hasPaper()) { removePaper() } if (count <= 99) { setBadgeText("$count") } } Imperative UI Showing/hiding Fire
  32. Shifting paradigms medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050 fun updateCount(count: Int) { if (count >

    0 && !hasBadge()) { addBadge() } else if (count == 0 && hasBadge()) { removeBadge() } if (count > 99 && !hasFire()) { addFire() setBadgeText("99+") } else if (count <= 99 && hasFire()) { removeFire() } if (count > 0 && !hasPaper()) { addPaper() } else if (count == 0 && hasPaper()) { removePaper() } if (count <= 99) { setBadgeText("$count") } } Imperative UI Showing/hiding Paper
  33. Shifting paradigms medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050 fun updateCount(count: Int) { if (count >

    0 && !hasBadge()) { addBadge() } else if (count == 0 && hasBadge()) { removeBadge() } if (count > 99 && !hasFire()) { addFire() setBadgeText("99+") } else if (count <= 99 && hasFire()) { removeFire() } if (count > 0 && !hasPaper()) { addPaper() } else if (count == 0 && hasPaper()) { removePaper() } if (count <= 99) { setBadgeText("$count") } } Imperative UI Showing/hiding Paper
  34. Shifting paradigms medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050 fun updateCount(count: Int) { if (count >

    0 && !hasBadge()) { addBadge() } else if (count == 0 && hasBadge()) { removeBadge() } if (count > 99 && !hasFire()) { addFire() setBadgeText("99+") } else if (count <= 99 && hasFire()) { removeFire() } if (count > 0 && !hasPaper()) { addPaper() } else if (count == 0 && hasPaper()) { removePaper() } if (count <= 99) { setBadgeText("$count") } } Imperative UI Badge number
  35. • compose.animation • Animations library to enrich user experience •

    compose.material • Ready to use material design components • compose.foundation • Building blocks • compose.ui • Ui components: layout, drawing, and input • Compose.runtime • Model, state management and core runtime • compose.compiler • Transform @composable functions and enable optimizations Jetpack compose
  36. • compose.animation • Animations library to enrich user experience •

    compose.material • Ready to use material design components • compose.foundation • Building blocks • compose.ui • Ui components: layout, drawing, and input • Compose.runtime • Model, state management and core runtime • compose.compiler • Transform @composable functions and enable optimizations Jetpack compose Compose compiler Compose Runtime Compose UI Toolkit (ANdroid) Compose animation Compose UI Compose Foundation Compose Material
  37. • compose.animation • Animations library to enrich user experience •

    compose.material • Ready to use material design components • compose.foundation • Building blocks • compose.ui • Ui components: layout, drawing, and input • Compose.runtime • Model, state management and core runtime • compose.compiler • Transform @composable functions and enable optimizations Jetpack compose Compose compiler Compose UI Toolkit (ANdroid) Compose animation Compose UI Compose Foundation Compose Material Compose Runtime
  38. Compose compiler • Written in Kotlin • Transforms @Composable functions

    into UI • Doesn’t use the Annotation Processor • The plugin works at system/code generation level • Doesn’t impact build times • Open source • Available in AOSP Compose compiler Compose UI Toolkit (ANdroid) Compose animation Compose UI Compose Foundation Compose Material Compose Runtime android.googlesource.com/platform/frameworks/support/+/HEAD/compose/compiler
  39. Compose Runtime • Platform agnostic • DOESN’T KNOW WHAT ANDROID

    OR UI ARE • TrEE MANAGEMENT SOLUTION Compose compiler Compose Runtime Compose UI Toolkit (ANdroid) Compose animation Compose UI Compose Foundation Compose Material
  40. Compose COMPILER/Runtime What this means is that Compose is, at

    its core, a general-purpose tool for managing a tree of nodes of any type. Well a “tree of nodes” describes just about anything, and as a result Compose can target just about anything. Jake Wharton - a Jetpack Compose by Any other name jakewharton.com/a-jetpack-compose-by-any-other-name/
  41. • compose.animation • Animations library to enrich user experience •

    compose.material • Ready to use material design components • compose.foundation • Building blocks • compose.ui • Ui components: layout, drawing, and input • Compose.runtime • Model, state management and core runtime • compose.compiler • Transform @composable functions and enable optimizations Jetpack compose Compose compiler Compose Runtime Compose UI Toolkit (ANdroid) Compose animation Compose UI Compose Foundation Compose Material
  42. Compose UI toolkit • compose.ui • Handles Input Management, Drawing,

    Layouts, etc. • Compose.foundation • Contains Basic building: Row, Column, Text, iMage, etc. • Compose.material • Material design system to use on your view components • Compose.animation • Animations to use easily and out side of the box Compose compiler Compose Runtime Compose UI Toolkit (ANdroid) Compose animation Compose Foundation Compose Material Compose UI
  43. Compose UI Raspberry pi compose Compose compiler Compose Runtime Compose

    UI ANdroid Compose UI Desktop Compose UI Web Compose UI Console
  44. medium.com/androiddevelopers/jetpack-compose-before-and-after-8b43ba0b7d4f Jetpack compose - before and after By Chris Banes

    APK Size 1250 2500 3750 5000 Pre-Compose (4.49 MB) Compose (2.39 MB) 4.14 MB 2.32 MB 69 KB 347 KB 4.49MB 2.32 MB -46%
  45. medium.com/androiddevelopers/jetpack-compose-before-and-after-8b43ba0b7d4f Jetpack compose - before and after By Chris Banes

    Method 12500 25000 37500 50000 Pre-Compose (40029) Compose (23689) 40029 23689 40029 methods 23689 methods -17% Count
  46. medium.com/androiddevelopers/jetpack-compose-before-and-after-8b43ba0b7d4f Jetpack compose - before and after By Chris Banes

    Lines of 5000 10000 15000 20000 Pre-Compose (19478) Compose (15407) 15827 14606 19478 Lines of code 15407 lines of code Source Code 3651 801
  47. medium.com/androiddevelopers/jetpack-compose-before-and-after-8b43ba0b7d4f Jetpack compose - before and after By Chris Banes

    Build 30 60 90 120 Pre-Compose (108,71 s) Compose (76,96 s) 108,71 76,96 108,71 seconds 76,96 seconds -29% Speed
  48. github.com/google/accompanist Accompanist 📐 Insets 🍫 System UI Controller 🎨 AppCompat

    Theme Adapter 🧭✨Navigation-Animation 🧭🎨 Navigation-Material 🖌 Drawable Painter ⬇ Swipe to Refresh 📖 Pager 📫 Permissions ⏳ Placeholder 🌊 Flow Layouts
  49. github.com/google/accompanist Accompanist 📐 Insets 🍫 System UI Controller 🎨 AppCompat

    Theme Adapter 🧭✨Navigation-Animation 🧭🎨 Navigation-Material 🖌 Drawable Painter ⬇ Swipe to Refresh 📖 Pager 📫 Permissions ⏳ Placeholder 🌊 Flow Layouts Keyboard IME animations are now available starting on minSDK 21 (through Jetpack Compose)
  50. Configuration android { defaultConfig { minSdk 21 … } kotlinOptions

    { jvmTarget = '1.8' useIR = true } buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion compose_version kotlinCompilerVersion '1.5.10' } } build.gradle
  51. Configuration android { defaultConfig { minSdk 21 … } kotlinOptions

    { jvmTarget = '1.8' useIR = true } buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion compose_version kotlinCompilerVersion '1.5.10' } } build.gradle
  52. Configuration android { defaultConfig { minSdk 21 … } kotlinOptions

    { jvmTarget = '1.8' useIR = true } buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion '1.0.0' kotlinCompilerVersion '1.5.10' } } build.gradle
  53. Configuration android { defaultConfig { minSdk 21 … } kotlinOptions

    { jvmTarget = '1.8' useIR = true } buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion '1.0.0' kotlinCompilerVersion '1.5.10' } } build.gradle
  54. Adding text <TextView android:id="@+id/tv_greetings" android:layout_width="wrap_content" android:layout_height=“wrap_content" android:text="Hello world!" /> XML

    Hello Android! Without Compose val greetings = findViewById(R.id.tv_greetings) greetings.text = "Hello Android!” Kt
  55. View Groups Hello world! Without compose <?xml version="1.0" encoding="utf-8"?> <LinearLayout

    xmlns:android="http: // schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_greetings" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello world!" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Number of coffees: 0" /> </ LinearLayout> XML Number of co ff ees: 0
  56. View Groups Hello world! With compose @Composable fun Greeting(name: String)

    { Text(text = "Hello $name!”) Text(text = "Number of coffees: 0") } Kt Number of co ff ees: 0
  57. View Groups Hello world! With compose @Composable fun Greeting(name: String)

    { Column { Text(text = "Hello $name!") Text(text = "Number of coffees: 0") } } Kt Number of co ff ees: 0
  58. View Groups Hello world! With compose @Composable fun Greeting(name: String)

    { Column { Text(text = "Hello $name!") Text(text = "Number of coffees: 0") } } Kt Number of co ff ees: 0
  59. View Groups Hello world! With compose @Composable fun Greeting(name: String)

    { Row { Text(text = "Hello $name!") Text(text = "Number of coffees: 0") } } Kt Number of co ff ees: 0
  60. Number of co ff ees: 0 View Groups With compose

    @Composable fun Greeting(name: String) { Column( modifier = Modifier.background(Color.Green) ){ Text(text = "Hello $name!") Text(text = "Number of coffees: 0") } } Kt Hello world!
  61. View Groups With compose @Composable fun Greeting(name: String) { Column(

    modifier = Modifier.background(Color.Green) ){ Text(text = "Hello $name!") Text(text = "Number of coffees: 0") } } Kt Hello world! Number of co ff ees: 0
  62. Hello world! Number of co ff ees: 0 View Groups

    With compose @Composable fun Greeting(name: String) { Column( modifier = Modifier.fillMaxSize() .background(Color.Green) ){ Text(text = "Hello $name!") Text(text = "Number of coffees: 0") } } Kt
  63. Hello world! Number of co ff ees: 0 View Groups

    With compose @Composable fun Greeting(name: String) { Column( modifier = Modifier.fillMaxSize() .background(Color.Green) .padding(16.dp) ){ Text(text = "Hello $name!") Text(text = "Number of coffees: 0") } } Kt
  64. Hello world! Number of co ff ees: 0 View Groups

    With compose @Composable fun Greeting(name: String) { Column( modifier = Modifier.fillMaxSize() .background(Color.Green) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ){ Text(text = "Hello $name!") Text(text = "Number of coffees: 0") } } Kt
  65. Hello world! View Groups With compose @Composable fun Greeting(name: String)

    { Column( modifier = Modifier.fillMaxSize() .background(Color.Green) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ){ Text( text = "Hello $name!”, color = Color.White ) Text(text = "Number of coffees: 0") } } Kt Number of co ff ees: 0
  66. Hello world! Images Without compose <?xml version="1.0" encoding="utf-8"?> <LinearLayout …

    <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher" android:contentDescription="@string/app_name" /> </ LinearLayout> XML Number of co ff ees: 0
  67. Hello world! Images With compose @Composable fun Greeting(name: String) {

    Column(…) { Text( text = "Hello $name!", color = Color.White ) Text(text = "Number of coffees: 0") Image( painter = painterResource(id = R.drawable.ic_launcher), contentDescription = stringResource(id = R.string.app_name) ) } } Kt Number of co ff ees: 0
  68. Hello world! Images With compose @Composable fun Greeting(name: String) {

    Column(…) { Text( text = "Hello $name!", color = Color.White ) Text(text = "Number of coffees: 0") Image( painter = painterResource(id = R.drawable.ic_launcher), contentDescription = stringResource(id = R.string.app_name) ) } } Kt Number of co ff ees: 0
  69. Hello world! Button Without compose <?xml version="1.0" encoding="utf-8"?> <LinearLayout …

    <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="+" /> </ LinearLayout> XML Number of co ff ees: 0 +
  70. Hello world! Button With compose @Composable fun Greeting(name: String) {

    Column(…) { … Text(text = "Number of coffees: 0") … Button(onClick = { /* TODO */ }) { Text("+") } } } Kt Number of co ff ees: 0 +
  71. Hello world! Button With compose @Composable fun Greeting(name: String) {

    Column(…) { … Text(text = "Number of coffees: 0") … Button(onClick = { /* TODO */ }) { Text("+") } } } Kt Number of co ff ees: 0 +
  72. Hello world! State Without compose var count = 0 binding.tvMessage

    = getString(R.string.number_coffee, count) binding.btn_increment.setOnClickListener { count ++ } Kt Number of co ff ees: 0 +
  73. Hello world! State With compose @Composable fun Greeting(name: String) {

    Column(…) { … val count = remember { mutableStateOf(0) } Text(text = "Number of coffees taken: ${count.value}") … Button(onClick = { count.value = count.value + 1 }) { Text("+") } } } Kt Number of co ff ees: 0 +
  74. Hello world! State With compose @Composable fun Greeting(name: String) {

    Column(…) { … val count = remember { mutableStateOf(0) } Text(text = "Number of coffees taken: ${count.value}") … Button(onClick = { count.value = count.value + 1 }) { Text("+") } } } Kt Number of co ff ees: 0 +
  75. Hello world! State With compose @Composable fun Greeting(name: String) {

    Column(…) { … val count = remember { mutableStateOf(0) } Text(text = "Number of coffees taken: ${count.value}") … Button(onClick = { count.value = count.value + 1 }) { Text("+") } } } Kt Number of co ff ees: 0 +
  76. • Jetpack Compose samples (Google) • github.com/android/compose-samples • A curated

    list of Jetpack Compose libraries, projects, articles and resources • github.com/jetpack-compose/jetpack-compose-awesomeAnimations library to enrich user experience • Jetpack Compose Catalog • jetpackcompose.app • Jetpack/JetBrains Compose Playground • foso.github.io/Jetpack-Compose-Playground/ • Community • twitch.tv/intelligibabble • joebirch.co/tag/jetpack-compose/ • jorgecastillo.dev/ Resources