Demystifying Jetpack Compose

Demystifying Jetpack Compose

Android developers used to deal with XML to build UIs even if they love or hate. It can be very time consuming and sometimes hard to maintain if Designers requested very customized UI components or PM requested very dynamic UIs. Now, Jetpack Compose comes out and it helps developers to build UIs in declarative way. It can also be used together with XML, too. The potential of Jetpack Compose is limitless. I will present some under the hood stuffs, the potentials and how to integrate in existing apps.

C4db14b923561bc57fc10725abbecbcf?s=128

Lin Min Phyo

December 08, 2019
Tweet

Transcript

  1. Demystifying Jetpack Compose @Linminphyoe1 better.hr

  2. What’s in the talk • Old ui toolkit vs Jetpack

    Compose • Mental model for Jetpack Compose • Inside Jetpack Compose • Using Jetpack Compose
  3. Android UI Toolkit Old UI Toolkit vs Jetpack Compose

  4. Views

  5. Old UI Toolkit - XML <Button android:id="@+id/button_foo" ... />

  6. Old UI Toolkit - XML <LinearLayout> <Button android:id="@+id/button_foo" ... />

    </LinearLayout>
  7. Old UI Toolkit - XML <LinearLayout> <Button android:id="@+id/button_foo" ... />

    <RecyclerView android:id=“@+id/list_posts" ... /> <ProgressBar android:id="@+id/progress" ... /> </LinearLayout>
  8. Old UI Toolkit — Button public class Button

  9. Old UI Toolkit — Button public class Button extends TextView{

    ... }
  10. Old UI Toolkit — Button public class Button extends TextView{

    ... }
  11. Old UI Toolkit — Button public class Button extends TextView{

    ... } public class TextView extends View{ ... }
  12. Old UI Toolkit — LinearLayout public class LinearLayout

  13. Old UI Toolkit — LinearLayout public class LinearLayout extends ViewGroup{

    ... }
  14. Old UI Toolkit — LinearLayout public class LinearLayout extends ViewGroup{

    ... }
  15. Old UI Toolkit — LinearLayout public class LinearLayout extends ViewGroup{

    ... } public class ViewGroup extends View{ ... }
  16. So. What’s inside View?? Position Layout Drawing Scrolling Animation Touch

    Events Focus Handling Size Margin Padding
  17. Old UI Toolkit public class Button extends TextView{ ... }

    public class TextView extends View{ ... } Inheritance
  18. Jetpack Compose Declarative, Unbundled UI Toolkit

  19. Jetpack Compose •⚠ Pre-alpha currently, API might change • Open-source

    • API 21+ • Declarative style • Unbundled from OS
  20. Functions

  21. Clean Code, Page 35 Functions should do one thing. They

    should do it well. They should do it only. “
  22. Composable Functions

  23. Composable Functions @Composable fun Button() { ... } @Composable fun

    Switch() { ... } @Composable fun Row() { Button() Switch() } • Top-level functions • Requires calling context • View tree is created as a result of function call graph
  24. Composable Functions @Composable fun Button() { ... }

  25. Data flow in Composable Functions @Composable fun Button(text:String) { ...

    }
  26. Data flow in Composable Functions @Composable fun Button(text:String) { ...

    } Property goes down into function
  27. Data flow in Composable Functions @Composable fun Button(text:String) { ...

    }
  28. Data flow in Composable Functions @Composable fun Button(text:String, onClick:(() ->

    Unit)?) { ... } Event goes up from lamda
  29. Data flow in Composable Functions @Composable fun Button(text:String, onClick:(() ->

    Unit)?) { ... } // Calling side Button(text = "DevFestYangon", onClick = { // Do your actions here })
  30. Jetpack Compose vs Old UI Toolkit • Functions over Class

    • Composition over Inheritance • Feature Composition • UI Library built on top of Kotlin language features
  31. Custom views So much easier

  32. Creating Date View Dec 7 SAT

  33. Creating Date View Dec 7 SAT data class DateUIModel (

    val month:String, val date:String, val day: String )
  34. @Composable fun DateView(model: DateUIModel) { } data class DateUIModel (

    val month:String, val date:String, val day: String ) Dec 7 SAT
  35. @Composable fun DateView(model: DateUIModel) { Container(width = 64.dp, height =

    64.dp) { } } data class DateUIModel ( val month:String, val date:String, val day: String ) Size of the view Dec 7 SAT It is composable function, too.
  36. @Composable fun DateView(model: DateUIModel) { Container(width = 64.dp, height =

    64.dp) { } } data class DateUIModel ( val month:String, val date:String, val day: String ) Dec 7 SAT
  37. @Composable fun DateView(model: DateUIModel) { Container(width = 64.dp, height =

    64.dp) { DrawBorder(border = Border(Color.LightGray, 1.dp), shape = RoundedCornerShape(8.dp)) } } data class DateUIModel ( val month:String, val date:String, val day: String ) Border Dec 7 SAT
  38. @Composable fun DateView(model: DateUIModel) { Container(width = 64.dp, height =

    64.dp) { DrawBorder(border = Border(Color.LightGray, 1.dp), shape = RoundedCornerShape(8.dp)) } } data class DateUIModel ( val month:String, val date:String, val day: String ) Dec 7 SAT
  39. @Composable fun DateView(model: DateUIModel) { Container(width = 64.dp, height =

    64.dp) { DrawBorder(border = Border(Color.LightGray, 1.dp), shape = RoundedCornerShape(8.dp)) Column(crossAxisAlignment = CrossAxisAlignment.Center) { Text(model.month) Text(model.date) Text(model.day) } } } data class DateUIModel ( val month:String, val date:String, val day: String ) 3 Texts in a column Dec 7 SAT
  40. @Composable fun DateView(model: DateUIModel) { Container(width = 64.dp, height =

    64.dp) { DrawBorder(border = Border(Color.LightGray, 1.dp), shape = RoundedCornerShape(8.dp)) Column(crossAxisAlignment = CrossAxisAlignment.Center) { Text(model.month, style = +themeTextStyle { overline }) Text(model.date, style = +themeTextStyle { h6 }) Text(model.day, style = +themeTextStyle { overline }) } } } data class DateUIModel ( val month:String, val date:String, val day: String ) Add styles Dec 7 SAT
  41. Using Date View Dec 7 SAT // Model val date

    = DateUIModel("Dec" , "7" , “SAT") // Create DateView DateView(model = date)
  42. Date View Dec 7 SAT // Create DateView DateView(model =

    date)
  43. Date Range View Dec 7 SAT Dec 8 SUN

  44. Date Range View Dec 7 SAT Dec 8 SUN @Composable

    fun DateRangeView(startDate:DateUIModel, endDate:DateUIModel) { }
  45. Date Range View Dec 7 SAT Dec 8 SUN @Composable

    fun DateRangeView(startDate:DateUIModel, endDate:DateUIModel) { Row(crossAxisAlignment = CrossAxisAlignment.Center) { } }
  46. Date Range View Dec 7 SAT Dec 8 SUN @Composable

    fun DateRangeView(startDate:DateUIModel, endDate:DateUIModel) { Row(crossAxisAlignment = CrossAxisAlignment.Center) { DateView(model = startDate) SimpleImage(id = R.drawable.arrow, tint = Color.Gray ) DateView(model = endDate) } }
  47. Date Range View Dec 7 SAT Dec 8 SUN @Composable

    fun DateRangeView(startDate:DateUIModel, endDate:DateUIModel) { Row(crossAxisAlignment = CrossAxisAlignment.Center) { DateView(model = startDate) SimpleImage(id = R.drawable.arrow, tint = Color.Gray ) DateView(model = endDate) } } Start date view needs to be hidden if only end date is provided. How to do it??
  48. Date Range View Dec 7 SAT Dec 8 SUN @Composable

    fun DateRangeView(startDate:DateUIModel?, endDate:DateUIModel) { Row(crossAxisAlignment = CrossAxisAlignment.Center) { startDate?.let{ DateView(model = startDate) SimpleImage(id = R.drawable.arrow, tint = Color.Gray ) } DateView(model = endDate) } } Start date view needs to be hidden if only end date is provided. How to do it??
  49. Date Range View Dec 7 SAT Dec 8 SUN @Composable

    fun DateRangeView(startDate:DateUIModel?, endDate:DateUIModel) { Row(crossAxisAlignment = CrossAxisAlignment.Center) { startDate?.let{ DateView(model = startDate) SimpleImage(id = R.drawable.arrow, tint = Color.Gray ) } DateView(model = endDate) } } Start date view needs to be hidden if only end date is provided. How to do it??
  50. Inside Button in Jetpack Compose

  51. Button in Jetpack Compose Click Me

  52. Click Me @Composable fun Button( text: String, onClick: (() ->

    Unit)? = null ) { Surface { Ripple { Clickable{ Container { Text(text) } } } } }
  53. Click Me @Composable fun Button( text: String, onClick: (() ->

    Unit)? = null ) { Surface { Ripple { Clickable{ Container { Text(text) } } } } }
  54. Click Me @Composable fun Button( text: String, onClick: (() ->

    Unit)? = null ) { Surface { Ripple { Clickable { Container { Text(text) } } } } }
  55. Click Me @Composable fun Button( text: String, onClick: (() ->

    Unit)? = null ) { Surface { Ripple { Clickable{ Container(constraints = ButtonConstraints, padding = EdgeInsets(8.dp)){ Text(text) } } } } }
  56. Click Me @Composable fun Button( text: String, onClick: (() ->

    Unit)? = null ) { Surface { Ripple { Clickable(onClick = onClick) { Container(constraints = ButtonConstraints, padding = EdgeInsets(8.dp)) { Text(text) } } } } }
  57. Click Me @Composable fun Button( text: String, onClick: (() ->

    Unit)? = null ) { Surface { Ripple(color = Color.Green) { Clickable(onClick = onClick) { Container(constraints = ButtonConstraints, padding = EdgeInsets(8.dp)) { Text(text) } } } } }
  58. Click Me @Composable fun Button( text: String, onClick: (() ->

    Unit)? = null ) { Surface(RoundedCornerShape(4.dp), Color.Red, elevation = 4.dp) { Ripple(color = Color.Green) { Clickable(onClick = onClick) { Container(constraints = ButtonConstraints, padding = EdgeInsets(8.dp)) { Text(text) } } } } }
  59. @Composable fun Button( text: String, onClick: (() -> Unit)? =

    null ) { Surface(RoundedCornerShape(4.dp), Color.Red, elevation = 4.dp) { Ripple(color = Color.Green) { Clickable(onClick = onClick) { Container(constraints = ButtonConstraints, padding = EdgeInsets(8.dp)) { Text(text) } } } } } Click Me Property goes down through functions
  60. @Composable fun Button( text: String, onClick: (() -> Unit)? =

    null ) { Surface(RoundedCornerShape(4.dp), Color.Red, elevation = 4.dp) { Ripple(color = Color.Green) { Clickable(onClick = onClick) { Container(constraints = ButtonConstraints, padding = EdgeInsets(8.dp)) { Text(text) } } } } } Click Me Event go up via lamda
  61. Dispatching Composable Function

  62. Old Way override fun onCreate(){ setContentView(R.layout.screen_home.xml) } R.layout.screen_home.xml

  63. Old Way override fun onCreate(){ setContentView(R.layout.screen_home.xml) } R.layout.screen_home.xml

  64. Old Way override fun onCreate(){ setContentView(R.layout.screen_home.xml) } New Way @Composable

    fun HomeScreen(){ // Your stack of composable views } override fun onCreate(){ setContent { HomeScreen() } } R.layout.screen_home.xml
  65. setContent fun Activity.setContent( content: @Composable() () -> Unit ): CompositionContext?

    { val composeView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidComposeView ?: AndroidComposeView(this).also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(composeView.root, this) { WrapWithAmbients(composeView, this, coroutineContext) { content() } } }
  66. setContent fun Activity.setContent( content: @Composable() () -> Unit ): CompositionContext?

    { val composeView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidComposeView ?: AndroidComposeView(this).also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(composeView.root, this) { WrapWithAmbients(composeView, this, coroutineContext) { content() } } }
  67. setContent fun Activity.setContent( content: @Composable() () -> Unit ): CompositionContext?

    { val composeView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidComposeView ?: AndroidComposeView(this).also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(composeView.root, this) { WrapWithAmbients(composeView, this, coroutineContext) { content() } } }
  68. setContent fun Activity.setContent( content: @Composable() () -> Unit ): CompositionContext?

    { val composeView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidComposeView ?: AndroidComposeView(this).also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(composeView.root, this) { WrapWithAmbients(composeView, this, coroutineContext) { content() } } }
  69. setContent fun ViewGroup.setContent( content: @Composable() () -> Unit ): CompositionContext?

    { val composeView = if (childCount > 0) { getChildAt(0) as? AndroidComposeView } else { removeAllViews(); null } ?: AndroidComposeView(context).also { addView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(composeView.root, context) { WrapWithAmbients(composeView, context, coroutineContext) { content() }
  70. setContent fun ViewGroup.setContent( content: @Composable() () -> Unit ): CompositionContext?

    { val composeView = if (childCount > 0) { getChildAt(0) as? AndroidComposeView } else { removeAllViews(); null } ?: AndroidComposeView(context).also { addView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(composeView.root, this) { WrapWithAmbients(composeView, this, coroutineContext) { content() }
  71. AndroidComposeView class AndroidComposeView constructor(context: Context) : ViewGroup(context), Owner, DensityScope, SemanticsTreeProvider

  72. AndroidComposeView class AndroidComposeView constructor(context: Context) : ViewGroup(context), Owner, DensityScope, SemanticsTreeProvider

    Element responsible for providing the semantics tree of the hierarchy.
  73. Ambients • Composable function that provides data and it can

    be shared through by whole sub-hierarchy
  74. Default Ambients ContextAmbient CoroutineContextAmbient DensityAmbient FocusManagerAmbient TextInputServiceAmbient FontLoaderAmbient AutofillTreeAmbient AutofillAmbient

    ConfigurationAmbient AndroidComposeViewAmbient LayoutDirectionAmbient
  75. Material Theme Ambients @Composable fun MaterialTheme( colors: MaterialColors = MaterialColors(),

    typography: MaterialTypography = MaterialTypography(), children: @Composable() () -> Unit ) { Colors.Provider(value = colors) { Typography.Provider(value = typography) { CurrentTextStyleProvider(value = typography.body1) { MaterialRippleTheme { MaterialShapeTheme(children = children) } } } } }
  76. Ambients @Composable fun MaterialTheme( colors: MaterialColors = MaterialColors(), typography: MaterialTypography

    = MaterialTypography(), children: @Composable() () -> Unit ) { Colors.Provider(value = colors) { Typography.Provider(value = typography) { CurrentTextStyleProvider(value = typography.body1) { MaterialRippleTheme { MaterialShapeTheme(children = children) } } } } }
  77. Getting Ambient values MaterialTheme { // Getting ambient value val

    primaryColor = +themeColor { primary } }
  78. Getting Ambient values MaterialTheme { // Getting ambient value val

    primaryColor = +themeColor { primary } } MaterialTheme { Row { Card { // Getting ambient value val primaryColor = +themeColor { primary } } } }
  79. Using Composable view in old UI Toolkit

  80. Using Composable view in old UI Toolkit @Composable fun Greeting(text:

    String){ Text("Greetings $text") }
  81. Using Composable view in old UI Toolkit @Composable @GenerateView fun

    Greeting(text: String){ Text("Greetings $text") }
  82. Using Composable view in old UI Toolkit @Composable @GenerateView fun

    Greeting(text: String){ Text("Greetings $text") } <GreetingView android:id="@+id/greeting" app:text="@string/hello_world"/>
  83. Using Composable view in old UI Toolkit @Composable @GenerateView fun

    Greeting(text: String){ Text("Greetings $text") } <GreetingView android:id="@+id/greeting" app:text="@string/hello_world"/> val greeting:GreetingView = findViewById(R.id.greeting); greeting.setText(“Greetings Compose”);
  84. Does Jetpack Compose recompose the whole view tree if something

    has changed?
  85. Positional Memorization

  86. Gap Buffer

  87. Kotlin Compiler Plugin

  88. Recomposition

  89. States and Model

  90. @Composable fun Counter(){ val count = state { 0 }

    Button( text = "Count: $count.value", onPress = { count.value += 1 } ) } States and Model
  91. States and Model @Composable fun Counter(){ val count = state

    { 0 } Button( text = "Count: ${count.value}”, onPress = { count.value += 1 } ) }
  92. States and Model @Composable fun Counter(){ val count = state

    { 0 } Button( text = "Count: ${count.value}”, onPress = { count.value += 1 } ) } @Model class State<T>( var value : T )
  93. States and Model @Model data class DateUIModel( var month :

    String, var date : String, var day : String )
  94. Try Jetpack Compose • Android Studio 4.0 Canary • https://developer.android.com/jetpack/compose/

    setup#sample
  95. Jetpack Compose Packages Tooling, Runtime, Compiler

  96. Jetpack Compose Packages dependencies { def compose_version = "0.1.0-dev03" //

    Tooling // e.g. // @Preview for preview in Android Studio // @GenerateView for compatible with current UI Toolkit implementation "androidx.ui:ui-tooling:$compose_version" }
  97. Jetpack Compose Packages dependencies { def compose_version = "0.1.0-dev03" //

    Runtime implementation "androidx.compose:compose-runtime:$compose_version" }
  98. dependencies { def compose_version = "0.1.0-dev03" // Framework implementation "androidx.ui:ui-framework:$compose_version"

    // Layout functions // e.g. Column, Row, Table implementation "androidx.ui:ui-layout:$compose_version" // Material components equivalent implementation "androidx.ui:ui-material:$compose_version" // Foundation functions implementation "androidx.ui:ui-foundation:$compose_version" // Animations and transitions implementation “androidx.ui:ui-animation:$compose_version” } Jetpack Compose Packages
  99. Learn • https://developer.android.com/jetpack/compose/ tutorial • https://github.com/Mishkun/jetpack-compose-faq • https://kotlinlang.slack.com/archives/CJLTWPH7S • http://intelligiblebabble.com/compose-from-first-

    principles/
  100. Starting Composing Yours. Thank you ❤ @Linminphyoe1 https://lin.phyo.work https://speakerdeck.com/hashlin Get

    the slides @
  101. @Linminphyoe1 https://lin.phyo.work Take a ride through a Burmese tech scene

    made by Burmese, for Burmese https://anchor.fm/techshaw @vincentpaing