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

Litho: Best Practices for Building Efficient UI (Mobius 2019)

Litho: Best Practices for Building Efficient UI (Mobius 2019)

This year at Google I/O a new declarative UI framework Jetpack Compose was announced. After that Apple presented SwiftUI for iOS on WWDC. Declarative UI suddenly became a hype topic.

You definitely know that such approach in Android isn't new — there are lots of DSL wrappers on top of regular views. But what if the real declarative UI is not the future with wait for Compose's stability, but a present thing? I will tell you about Litho — UI framework from Facebook which is responsible for fast rendering of heavy UIs in apps with billions of downloads.

We will talk about Litho's usage, things that happen under the hood and will finally answer the question: is it really impossible to work with UI from more than one thread?

Video: https://youtu.be/4F4HJcFrWEw

C50a1f407bc251b7395c0984be4327e9?s=128

Sergey Ryabov

December 07, 2019
Tweet

More Decks by Sergey Ryabov

Other Decks in Programming

Transcript

  1. LITHO: BEST PRACTICES FOR BUILDING EFFICIENT UI Sergey Ryabov Facebook

  2. AGENDA ✦ What is Litho ✦ How does it work

    ✦ State management ✦ Animations ✦ Interop ✦ Tools
  3. WHAT IS LITHO

  4. WHAT IS LITHO

  5. WHAT IS LITHO ✦ UI framework

  6. WHAT IS LITHO ✦ UI framework ✦ 4.5 years old

  7. WHAT IS LITHO ✦ UI framework ✦ 4.5 years old

    ✦ Used in Facebook apps
  8. WHAT IS LITHO ✦ UI framework ✦ 4.5 years old

    ✦ Used in Facebook apps ✦ But not only...
  9. WHAT IS LITHO ✦ UI framework ✦ 4.5 years old

    ✦ Used in Facebook apps ✦ But not only... appbrain.com/stats/libraries/details/litho/litho
  10. WHAT IS LITHO ABOUT

  11. Inflate Measure Layout Draw WHAT IS LITHO ABOUT

  12. In Me La Dr WHAT IS LITHO ABOUT

  13. Infl Mea Lay Dra WHAT IS LITHO ABOUT

  14. UI BG Infl Mea Lay Dra WHAT IS LITHO ABOUT

  15. UI BG Infl WHAT IS LITHO ABOUT Mea Lay Dra

  16. UI BG Infl WHAT IS LITHO ABOUT Meas Layo Draw

  17. UI BG Infl Draw WHAT IS LITHO ABOUT Meas Layo

  18. UI BG Infl Draw WHAT IS LITHO ABOUT Meas Layo

  19. UI BG Infl Draw WHAT IS LITHO ABOUT Meas Layo

  20. WHAT IS LITHO ABOUT

  21. ✦ Asynchronous layout WHAT IS LITHO ABOUT

  22. UI BG Infl Draw WHAT IS LITHO ABOUT Meas Layo

  23. UI BG Infl Draw WHAT IS LITHO ABOUT Meas Layo

  24. UI BG Infl Draw WHAT IS LITHO ABOUT Meas Layo

    yogalayout.com
  25. ✦ Asynchronous layout WHAT IS LITHO ABOUT

  26. WHAT IS LITHO ABOUT ✦ Asynchronous layout ✦ Declarative API

  27. UI = f(properties)

  28. UI = f(props)

  29. fun f( title: String, subtitle: String ): UI { return

    Column.create() .child( Text.create() .text(title)) .child( Text.create() .text(subtitle)) .build() }
  30. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop title: String, @Prop subtitle:

    String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() }
  31. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop title: String, @Prop subtitle:

    String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() }
  32. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  33. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  34. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  35. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .paddingDip(ALL, 16f) .child( Text.create(c) .text(title) .textSizeSp(20f)) .child( Text.create(c) .text(subtitle)) .build() } }
  36. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  37. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } } @LayoutSpec object ListItemWithImageSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop image: Drawable, @Prop title: String, @Prop subtitle: String): Component { return Row.create(c) .child( Image.create(c) .drawable(image)) .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } }
  38. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } } @LayoutSpec object ListItemWithImageSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop image: Drawable, @Prop title: String, @Prop subtitle: String): Component { return Row.create(c) .child( Image.create(c) .drawable(image)) .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } }
  39. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } } @LayoutSpec object ListItemWithImageSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop image: Drawable, @Prop title: String, @Prop subtitle: String): Component { return Row.create(c) .child( Image.create(c) .drawable(image)) .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } }
  40. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } } @LayoutSpec object ListItemWithImageSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop image: Drawable, @Prop title: String, @Prop subtitle: String): Component { return Row.create(c) .child( Image.create(c) .drawable(image)) .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } }
  41. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } } @LayoutSpec object ListItemWithImageSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop image: Drawable, @Prop title: String, @Prop subtitle: String): Component { return Row.create(c) .child( Image.create(c) .drawable(image)) .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } }
  42. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  43. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } } class MainActivity : AppCompatActivity() { override fun onCreate(state: Bundle?) { super.onCreate(savedInstanceState) val c = ComponentContext(this) setContentView(LithoView.create(c, ListItem.create(c) .title("Title") .subtitle("Subtitle") .build())) } }
  44. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } } class MainActivity : AppCompatActivity() { override fun onCreate(state: Bundle?) { super.onCreate(savedInstanceState) val c = ComponentContext(this) setContentView(LithoView.create(c, ListItem.create(c) .title("Title") .subtitle("Subtitle") .build())) } }
  45. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } } class MainActivity : AppCompatActivity() { override fun onCreate(state: Bundle?) { super.onCreate(savedInstanceState) val c = ComponentContext(this) setContentView(LithoView.create(c, ListItem.create(c) .title("Title") .subtitle("Subtitle") .build())) } }
  46. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } } class MainActivity : AppCompatActivity() { override fun onCreate(state: Bundle?) { super.onCreate(savedInstanceState) val c = ComponentContext(this) setContentView(LithoView.create(c, ListItem.create(c) .title("Title") .subtitle("Subtitle") .build())) } }
  47. @LayoutSpec object ListItemSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop

    title: String, @Prop subtitle: String ): Component { return Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } } class MainActivity : AppCompatActivity() { override fun onCreate(state: Bundle?) { super.onCreate(savedInstanceState) val c = ComponentContext(this) setContentView(LithoView.create(c, ListItem.create(c) .title("Title") .subtitle("Subtitle") .build())) } }
  48. HOW DOES IT ALL WORK?

  49. HOW DOES IT ALL WORK

  50. HOW DOES IT ALL WORK 1. Create an Internal Tree

  51. HOW DOES IT ALL WORK 1. Create an Internal Tree

    2. Create LayoutState
  52. HOW DOES IT ALL WORK 1. Create an Internal Tree

    2. Create LayoutState 3. Mount LayoutState
  53. @LayoutSpec object ListItemWithImageSpec { // ... Row.create(c) .child( Image.create(c) .drawable(image))

    .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } }
  54. @LayoutSpec object ListItemWithImageSpec { // ... Row.create(c) .child( Image.create(c) .drawable(image))

    .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } }
  55. @LayoutSpec object ListItemWithImageSpec { // ... Row.create(c) .child( Image.create(c) .drawable(image))

    .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } } ListItemWithImage
  56. @LayoutSpec object ListItemWithImageSpec { // ... Row.create(c) .child( Image.create(c) .drawable(image))

    .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } } Row ListItemWithImage
  57. @LayoutSpec object ListItemWithImageSpec { // ... Row.create(c) .child( Image.create(c) .drawable(image))

    .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } } Row ListItemWithImage
  58. @LayoutSpec object ListItemWithImageSpec { // ... Row.create(c) .child( Image.create(c) .drawable(image))

    .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build() } } Row ListItemWithImage Image
  59. @LayoutSpec object ListItemWithImageSpec { // ... Row.create(c) .child( Image.create(c) .drawable(image))

    .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build(). } } Row ListItemWithImage Image
  60. @LayoutSpec object ListItemWithImageSpec { // ... Row.create(c) .child( Image.create(c) .drawable(image))

    .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build(). } } Row ListItemWithImage Image ListItem
  61. Row ListItemWithImage Image ListItem ListItem.create(c) .title(title) .subtitle(subtitle)) .build(). } }

    @LayoutSpec object ListItemSpec { // ... Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  62. Row ListItemWithImage Image Column ListItem @LayoutSpec object ListItemSpec { //

    ... Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  63. Row ListItemWithImage Image Column ListItem @LayoutSpec object ListItemSpec { //

    ... Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  64. Row ListItemWithImage Image Column ListItem Text @LayoutSpec object ListItemSpec {

    // ... Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  65. Row ListItemWithImage Image Column ListItem Text @LayoutSpec object ListItemSpec {

    // ... Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  66. Row ListItemWithImage Image Column ListItem Text Text @LayoutSpec object ListItemSpec

    { // ... Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  67. Row ListItemWithImage Image Column ListItem Text Text @LayoutSpec object ListItemSpec

    { // ... Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  68. CREATE LAYOUTSTATE Row Image Column Text Text

  69. CREATE LAYOUTSTATE Measure with Yoga Row Image Column Text Text

  70. CREATE LAYOUTSTATE Measure with Yoga Row Image Column Text Text

    (0, 0 - 640, 96) (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80)
  71. CREATE LAYOUTSTATE Measure with Yoga Collect a list of items

    to be drawn and their positions Row Image Column Text Text (0, 0 - 640, 96) (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80)
  72. Row Image Column Text Text (0, 0 - 640, 96)

    (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80)
  73. Row Image Column Text Text (0, 0 - 640, 96)

    (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80) Root
  74. Root Row Image Column Text Text (0, 0 - 640,

    96) (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80)
  75. Root Image Row Image Column Text Text (0, 0 -

    640, 96) (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80)
  76. Root Image Row Image Column Text Text (0, 0 -

    640, 96) (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80)
  77. Root Text Image Row Image Column Text Text (0, 0

    - 640, 96) (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80)
  78. Root Text Image Row Image Column Text Text (0, 0

    - 640, 96) (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80) Text
  79. Root Text Image Row Image Column Text Text (0, 0

    - 640, 96) (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80) Text LayoutState
  80. Root Text Image Row Image Column Text Text (0, 0

    - 640, 96) (88, 0 - 640, 96) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80) Text Im ageDraw able TextDraw able TextDraw able LayoutState
  81. MOUNT LAYOUTSTATE Root Text Image Text Im ageDraw able TextDraw

    able TextDraw able LayoutState
  82. MOUNT LAYOUTSTATE The only step that always happens on UI

    thread Root Text Image Text Im ageDraw able TextDraw able TextDraw able LayoutState
  83. MOUNT LAYOUTSTATE The only step that always happens on UI

    thread Text Image Text Root Text Image Text Im ageDraw able TextDraw able TextDraw able LayoutState
  84. MOUNT LAYOUTSTATE The only step that always happens on UI

    thread Text Image Text Root Text Image Text Im ageDraw able TextDraw able TextDraw able LayoutState
  85. BUILDING BLOCKS

  86. BUILDING BLOCKS ✦ @LayoutSpec

  87. BUILDING BLOCKS ✦ @LayoutSpec ListItemWithImage ListItem

  88. BUILDING BLOCKS ✦ @LayoutSpec ✦ Row / Column

  89. BUILDING BLOCKS ✦ @LayoutSpec ✦ Row / Column Row Column

  90. BUILDING BLOCKS Image Text Text ✦ @LayoutSpec ✦ Row /

    Column
  91. BUILDING BLOCKS ✦ @LayoutSpec ✦ Row / Column ✦ Image

    Text Text ...? ✦ @LayoutSpec ✦ Row / Column
  92. BUILDING BLOCKS ✦ @LayoutSpec ✦ Row / Column ✦ Image

    Text Text ✦ @MountSpec ✦ @LayoutSpec ✦ Row / Column
  93. @MountSpec object GradientSpec { @OnCreateMountContent fun onCreateMountContent(context: Context): GradientDrawable {

    return GradientDrawable() } @OnMount fun onMount( c: ComponentContext, drawable: GradientDrawable, @Prop @ColorInt colors: IntArray) { drawable.colors = colors } }
  94. @MountSpec object GradientSpec { @OnCreateMountContent fun onCreateMountContent(context: Context): GradientDrawable {

    return GradientDrawable() } @OnMount fun onMount( c: ComponentContext, drawable: GradientDrawable, @Prop @ColorInt colors: IntArray) { drawable.colors = colors } }
  95. @MountSpec object GradientSpec { @OnCreateMountContent fun onCreateMountContent(context: Context): GradientDrawable {

    return GradientDrawable() } @OnMount fun.onMount( c: ComponentContext, drawable: GradientDrawable, @Prop @ColorInt colors: IntArray) { drawable.colors = colors } }
  96. @MountSpec object GradientSpec { @OnCreateMountContent fun onCreateMountContent(context: Context): GradientDrawable {

    return GradientDrawable() } @OnMount fun.onMount( c: ComponentContext, drawable: GradientDrawable, @Prop @ColorInt colors: IntArray) { drawable.colors = colors } }
  97. public @interface MountSpec { int poolSize() default.3; boolean isPureRender() default

    false; }
  98. public @interface MountSpec { int poolSize() default.3; boolean isPureRender() default

    false; }
  99. public @interface MountSpec { int poolSize() default.3; boolean isPureRender() default

    false; }
  100. public @interface MountSpec { int poolSize() default 3; boolean isPureRender()

    default false; } @MountSpec(poolSize = 30, isPureRender = true) class ImageSpec { @ShouldUpdate static boolean shouldUpdate(...) {} }
  101. public @interface MountSpec { int poolSize() default 3; boolean isPureRender()

    default false; } @MountSpec(poolSize = 30, isPureRender = true) class ImageSpec { @ShouldUpdate static boolean shouldUpdate(...) {} }
  102. OPTIMIZATIONS

  103. OPTIMIZATIONS ✦ Layout/Mount Diffing

  104. OPTIMIZATIONS ✦ Layout/Mount Diffing • Reuse Measurements

  105. OPTIMIZATIONS ✦ Layout/Mount Diffing • Reuse Measurements • Reuse LayoutOutputs

  106. Text Img Text (16, 16 - 80, 80) (88, 16

    - 624, 48) (88, 48 - 624, 80) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80) Internal Tree Diff Tree
  107. Text Img Text (16, 16 - 80, 80) (88, 16

    - 624, 48) (88, 48 - 624, 80) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80) Internal Tree Diff Tree
  108. Text Img Text (16, 16 - 80, 80) (88, 16

    - 624, 48) (88, 48 - 624, 80) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80) Internal Tree Diff Tree
  109. Text Img Text (16, 16 - 80, 80) (88, 16

    - 624, 48) (88, 48 - 624, 80) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80) Internal Tree Diff Tree
  110. (16, 16 - 80, 80) (88, 16 - 624, 48)

    (88, 48 - 624, 80) Text Text Img (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80) Root LayoutState Internal Tree Diff Tree
  111. (16, 16 - 80, 80) (88, 16 - 624, 48)

    (88, 48 - 624, 80) (16, 16 - 80, 80) (88, 16 - 624, 48) (88, 48 - 624, 80) Root Text Text Image LayoutState Internal Tree Diff Tree
  112. OPTIMIZATIONS ✦ Layout/Mount Diffing • Reuse Measurements • Reuse LayoutOutputs

  113. OPTIMIZATIONS ✦ Layout/Mount Diffing • Reuse Measurements • Reuse LayoutOutputs

    ✦ IncrementalMount
  114. INCREMENTAL MOUNT

  115. INCREMENTAL MOUNT

  116. INCREMENTAL MOUNT

  117. INCREMENTAL MOUNT

  118. INCREMENTAL MOUNT

  119. INCREMENTAL MOUNT

  120. GOTCHAS

  121. GOTCHAS ✦ Use isPureRender / @ShouldUpdate to reuse previous layout

    results for @MountSpecs
  122. GOTCHAS ✦ Use isPureRender / @ShouldUpdate to reuse previous layout

    results for @MountSpecs ✦ Tweak MountPools according to you requirements
  123. MANAGING STATE

  124. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @State count: Int): Component {

    return Column.create(c) .child( Text.create(c) .text("$count") ) .child( Text.create(c) .text("+") ) .build() }
  125. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @State count: Int): Component {

    return Column.create(c) .child( Text.create(c) .text("$count") ) .child( Text.create(c) .text("+") ) .build() }
  126. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @State count: Int): Component {

    return Column.create(c) .child( Text.create(c) .text("$count") ) .child( Text.create(c) .text("+") ) .build() }
  127. 
 
 @OnUpdateState fun increment(count: StateValue<Int>) { count.set(count.get() + 1)

    }
  128. 
 @OnUpdateState fun increment(count: StateValue<Int>) { count.set(count.get() + 1) }


    
 @OnEvent(ClickEvent::class) fun.onIncrement(c: ComponentContext) { RootComponent.increment(c) }
  129. 
 @OnUpdateState fun increment(count: StateValue<Int>) { count.set(count.get() + 1) }


    
 @OnEvent(ClickEvent::class) fun.onIncrement(c: ComponentContext) { RootComponent.increment(c) } // ... .child( Text.create(c) .text("+") .clickHandler(RootComponent.onIncrement(c)) )
  130. @OnUpdateState fun increment(count: StateValue<Int>) { count.set(count.get() + 1) }
 


    @OnEvent(ClickEvent::class) fun.onIncrement(c: ComponentContext) { RootComponent.increment(c) }
  131. @OnUpdateState fun increment(count: StateValue<Int>) { count.set(count.get() + 1) }
 


    RootComponent.increment(c)
  132. State updates? I'll take two!

  133. @OnUpdateState fun changeColor(color: StateValue<Color>, @Param newColor: Color) { color.set(newColor) }

    @OnUpdateState fun changeName(name: StateValue<String>, @Param newName: String) { name.set(newName) }
  134. @OnUpdateState fun changeColor(color: StateValue<Color>, @Param newColor: Color) { color.set(newColor) }

    @OnUpdateState fun changeName(name: StateValue<String>, @Param newName: String) { name.set(newName) } ... ... RootComponent.changeColor(c, Color.RED) RootComponent.changeName(c, "IronMan")
  135. None
  136. None
  137. None
  138. None
  139. @OnUpdateState fun changeColor(color: StateValue<Color>, @Param newColor: Color) { color.set(newColor) }

    @OnUpdateState fun changeName(name: StateValue<String>, @Param newName: String) { name.set(newName) } ... ... RootComponent.changeColor(c, Color.RED) RootComponent.changeName(c, "IronMan")
  140. @OnUpdateState fun changeHero( color: StateValue<Color>, name: StateValue<String>, @Param newColor: Color,

    @Param newName: String) { color.set(newColor) name.set(newName) } ... ... RootComponent.changeHero(c, Color.RED, "IronMan")
  141. None
  142. None
  143. What if State is not tied to rendering?

  144. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @State count: Int, ): Component

    { return Column.create(c) // ... .build() }
  145. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @State count: Int, @State(canUpdateLazily =

    true) step: Int ): Component { return Column.create(c) // ... .child( TextInput.create(c).initialText("$step")) .build() }
  146. @State(canUpdateLazily = true) step: Int RootComponent.lazyUpdateStep(c, value)

  147. @State(canUpdateLazily = true) step: Int RootComponent.lazyUpdateStep(c, value)

  148. GOTCHAS

  149. GOTCHAS ✦ Batch your State updates

  150. GOTCHAS ✦ Batch your State updates ✦ Use lazy State

    updates where possible
  151. ANIMATE ALL THE THINGS

  152. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @State alignToEnd: Boolean): Component {

    val align = if (alignToEnd) FLEX_END else FLEX_START return Column.create(c) .justifyContent(CENTER) .child( Button.create(c) .text("Catch me!") .alignSelf(align) .clickHandler(RootComponent.onClick(c)) ) .build() }
  153. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @State alignToEnd: Boolean): Component {

    val align = if (alignToEnd) FLEX_END else FLEX_START return Column.create(c) .justifyContent(CENTER) .child( Button.create(c) .text("Catch me!") .alignSelf(align) .clickHandler(RootComponent.onClick(c)) ) .build() }
  154. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @State alignToEnd: Boolean): Component {

    val align = if (alignToEnd) FLEX_END else FLEX_START return Column.create(c) .justifyContent(CENTER) .child( Button.create(c) .text("Catch me!") .transitionKey("button") .alignSelf(align) .clickHandler(RootComponent.onClick(c)) ) .build() }
  155. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @State alignToEnd: Boolean): Component {

    val align = if (alignToEnd) FLEX_END else FLEX_START return Column.create(c) .justifyContent(CENTER) .child( Button.create(c) .text("Catch me!") .transitionKey("button") .alignSelf(align) .clickHandler(RootComponent.onClick(c)) ) .build() } @OnCreateTransition fun.onCreateTransition(c: ComponentContext): Transition { return.Transition.create("button") .animate(AnimatedProperties.X) }
  156. @OnCreateLayout fun onCreateLayout( c: ComponentContext, @State alignToEnd: Boolean): Component {

    val align = if (alignToEnd) FLEX_END else FLEX_START return Column.create(c) .justifyContent(CENTER) .child( Button.create(c) .text("Catch me!") .transitionKey("button") .alignSelf(align) .clickHandler(RootComponent.onClick(c)) ) .build() } @OnCreateTransition fun.onCreateTransition(c: ComponentContext): Transition { return.Transition.create("button") .animate(AnimatedProperties.X) }
  157. @OnCreateTransition fun.onCreateTransition(c: ComponentContext): Transition { return.Transition.create("button") .animate(AnimatedProperties.X) }

  158. @OnCreateTransition fun.onCreateTransition(c: ComponentContext): Transition { return.Transition.allLayout() }

  159. ANIMATIONS

  160. ANIMATIONS onCreateTransition is called on every onCreateLayout

  161. ANIMATIONS onCreateTransition is called on every onCreateLayout Even if you

    don't need to animate that specific UI update
  162. ANIMATIONS onCreateTransition is called on every onCreateLayout Even if you

    don't need to animate that specific UI update More granular control?
  163. @OnCreateTransition fun onCreateTransition(c: ComponentContext): Transition { return Transition.allLayout() }

  164. @OnCreateTransition fun onCreateTransition(c: ComponentContext, @Prop prop: Diff<String>, @State state: Diff<Boolean>):

    Transition? { return if (canAnimate(prop, state)) Transition.allLayout() else null }
  165. @OnCreateTransition fun onCreateTransition(c: ComponentContext, @Prop prop: Diff<String>, @State state: Diff<Boolean>):

    Transition? { return if (canAnimate(prop, state)) Transition.allLayout() else null }
  166. @OnCreateTransition fun onCreateTransition(c: ComponentContext, @Prop prop: Diff<String>, @State state: Diff<Boolean>):

    Transition? { return if (canAnimate(prop, state)) Transition.allLayout() else null } public final class Diff<T> { public T getPrevious() public T getNext() }
  167. GOTCHAS

  168. GOTCHAS Use Diff<T> for more granular control over transitions

  169. GRADUAL ADOPTION

  170. GRADUAL ADOPTION

  171. GRADUAL ADOPTION Can't migrate the whole screen at once? Do

    step by step:
  172. GRADUAL ADOPTION Can't migrate the whole screen at once? Do

    step by step: • Complex UI parts can be replaced with a LithoView and underlying Component structure
  173. GRADUAL ADOPTION Can't migrate the whole screen at once? Do

    step by step: • Complex UI parts can be replaced with a LithoView and underlying Component structure • Custom Views can be wrapped in MountSpecs
  174. SOMETHING DOESN'T WORK!

  175. TOOLS

  176. TOOLS Yoga Playground

  177. yogalayout.com/playground

  178. TOOLS Yoga Playground

  179. TOOLS Yoga Playground Flipper + Layout plugin

  180. fbflipper.com

  181. TOOLS Yoga Playground Flipper + Layout plugin

  182. TOOLS Yoga Playground Flipper + Layout plugin Litho IntelliJ Plugin

    (Beta)
  183. None
  184. github.com/facebook/litho/tree/master/litho-intellij-plugin

  185. TOOLS Yoga Playground Flipper + Layout plugin Litho IntelliJ Plugin

    (Beta)
  186. TOOLS Yoga Playground Flipper + Layout plugin Litho IntelliJ Plugin

    (Beta) Lithography Sample app
  187. None
  188. WHAT IS LITHO ✦ Asynchronous layout ✦ Declarative API ✦

    Flatter view hierarchies ✦ Fine-grained recycling
  189. RESOURCES ✦ Official Docs: fblitho.com ✦ Lithography Sample app: github.com/facebook/litho/tree/master/sample

    ✦ Litho Codelabs (preview): github.com/facebook/litho/tree/master/codelabs ✦ Dive into Litho Threading Model: youtu.be/78BUH1LZaZ4
  190. THAT'S ALL FOLKS

  191. THAT'S NOT ALL FOLKS

  192. THAT'S NOT ALL FOLKS

  193. Text(text = "Hello, Kotlin World!", textSize = 20.sp)

  194. Column { +Text(text = "Hello, Kotlin World!", textSize = 20.sp)

    +Text( text = "with ❤ from London", textStyle = Typeface.ITALIC) }
  195. Padding(all = 16.dp) { Column { +Text(text = "Hello, Kotlin

    World!", textSize = 20.sp) +Text( text = "with ❤ from London", textStyle = Typeface.ITALIC) } }
  196. Clickable(onClick = { }) { Padding(all = 16.dp) { Column

    { +Text(text = "Hello, Kotlin World!", textSize = 20.sp) +Text( text = "with ❤ from London", textStyle = Typeface.ITALIC) } } }
  197. val counter by useState { 1 } Clickable(onClick = {

    updateState { counter.value = counter.value + 1 } }) { Padding(all = 16.dp) { Column { +Text(text = "Hello, Kotlin World!", textSize = 20.sp) +Text( text = "with ${"❤".repeat(counter.value)} from London", textStyle = Typeface.ITALIC) } } }
  198. setContent { val counter by useState { 1 } Clickable(onClick

    = { updateState { counter.value = counter.value + 1 } }) { Padding(all = 16.dp) { Column { +Text(text = "Hello, Kotlin World!", textSize = 20.sp) +Text( text = "with ${"❤".repeat(counter.value)} from London", textStyle = Typeface.ITALIC) } } } }
  199. class PlaygroundActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContent { val counter by useState { 1 } Clickable(onClick = { updateState { counter.value = counter.value + 1 } }) { Padding(all = 16.dp) { Column { +Text(text = "Hello, Kotlin World!", textSize = 20.sp) +Text( text = "with ${"❤".repeat(counter.value)} from London", textStyle = Typeface.ITALIC) } } } } } }
  200. THANK YOU Sergey Ryabov @colriot

  201. Text.create( c, android.R.attr.buttonStyle, 0 ) Text.create( c, 0, R.style.Widget_AppCompat_Button_Colored )