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

Litho: Best Practices for Building Efficient UI (Droidcon NYC 2019)

Litho: Best Practices for Building Efficient UI (Droidcon NYC 2019)

Litho is a declarative Android UI framework which allows you to build more performant and efficient UIs than usual. With Litho you can move your layout phase to the background thread, recycle not only list items, but even individual widgets and do more granular UI updates. It's a very powerful tool with lots of features and possible settings.

In this talk I'll discuss best practices and tweaks that you can apply in difficult cases to get the maximum performance out of the framework.

Video: https://www.droidcon.com/media-detail?video=362740949

C50a1f407bc251b7395c0984be4327e9?s=128

Sergey Ryabov

August 27, 2019
Tweet

More Decks by Sergey Ryabov

Other Decks in Programming

Transcript

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

  2. WHAT IS LITHO

  3. WHAT IS LITHO

  4. WHAT IS LITHO Declarative API

  5. WHAT IS LITHO Declarative API Asynchronous layout

  6. WHAT IS LITHO Declarative API Asynchronous layout Flatter view hierarchies

  7. WHAT IS LITHO Declarative API Asynchronous layout Flatter view hierarchies

    Fine-grained recycling
  8. @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() } }
  9. @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() } }
  10. @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() } }
  11. @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() } }
  12. @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() } }
  13. @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() } }
  14. @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() } }
  15. @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() } }
  16. @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())) } }
  17. @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())) } }
  18. @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())) } }
  19. @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())) } }
  20. @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())) } }
  21. @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() } }
  22. @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(resType=DRAWABLE) 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() } }
  23. @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(resType=DRAWABLE) 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() } }
  24. @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(resType=DRAWABLE) 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() } }
  25. @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(resType=DRAWABLE) 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() } }
  26. HOW DOES IT ALL WORK?

  27. HOW DOES IT ALL WORK

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

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

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

    2. Create LayoutState 3. Mount LayoutState
  31. @LayoutSpec object ListItemWithImageSpec { @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop(resType=DRAWABLE)

    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() } }
  32. @LayoutSpec object ListItemWithImageSpec { // ... Row.create(c) .child( Image.create(c) .drawable(image))

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

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

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

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

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

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

    .child( ListItem.create(c) .title(title) .subtitle(subtitle)) .build(). } } Row ListItemWithImage Image
  39. @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
  40. 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() } }
  41. Row ListItemWithImage Image Column ListItem @LayoutSpec object ListItemSpec { //

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

    ... Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  43. 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() } }
  44. 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() } }
  45. 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() } }
  46. 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() } }
  47. CREATE LAYOUTSTATE Row Image Column Text Text

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

  49. 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)
  50. 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)
  51. 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)
  52. 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
  53. 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)
  54. 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)
  55. 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)
  56. 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)
  57. 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)
  58. 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)
  59. 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)
  60. 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
  61. 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
  62. 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
  63. MOUNT LAYOUTSTATE Root Text Image Text Im ageDraw able TextDraw

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

    thread Root Text Image Text Im ageDraw able TextDraw able TextDraw able LayoutState
  65. Root Text Image Text Im ageDraw able TextDraw able TextDraw

    able LayoutState Row Image Column Text Text
  66. @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 } // ... }
  67. @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 } // ... }
  68. @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 } // ... }
  69. @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 } // ... }
  70. public @interface MountSpec { int poolSize() default.3; boolean isPureRender() default

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

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

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

    default false; } @MountSpec(poolSize = 30, isPureRender = true) class ImageSpec {
  74. OPTIMIZATIONS

  75. OPTIMIZATIONS Layout/Mount Diffing

  76. OPTIMIZATIONS Layout/Mount Diffing • Reuse Measurements

  77. OPTIMIZATIONS Layout/Mount Diffing • Reuse Measurements • Reuse LayoutOutputs

  78. 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
  79. 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
  80. 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
  81. 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
  82. (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
  83. (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
  84. OPTIMIZATIONS Layout/Mount Diffing • Reuse Measurements • Reuse LayoutOutputs

  85. OPTIMIZATIONS Layout/Mount Diffing • Reuse Measurements • Reuse LayoutOutputs IncrementalMount

  86. INCREMENTAL MOUNT

  87. INCREMENTAL MOUNT

  88. INCREMENTAL MOUNT

  89. INCREMENTAL MOUNT

  90. INCREMENTAL MOUNT

  91. INCREMENTAL MOUNT

  92. GOTCHAS

  93. GOTCHAS Use isPureRender / @ShouldUpdate to reuse previous layout results

  94. GOTCHAS Use isPureRender / @ShouldUpdate to reuse previous layout results

    Tweak MountPools according to you requirements
  95. MANAGING STATE

  96. @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() }
  97. @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() }
  98. 
 
 @OnUpdateState fun increment(count: StateValue<Int>) { count.set(count.get() + 1)

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


    
 @OnEvent(ClickEvent::class) fun.onIncrement(c: ComponentContext) { RootComponent.increment(c) }
  100. 
 @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)) )
  101. @OnUpdateState fun increment(count: StateValue<Int>) { count.set(count.get() + 1) }
 


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


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


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

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

    @OnUpdateState fun changeName(name: StateValue<String>, @Param newName: String) { name.set(newName) }
  106. @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.BLUE) RootComponent.changeName(c, "Plum")
  107. None
  108. None
  109. None
  110. @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.BLUE) RootComponent.changeName(c, "Plum")
  111. @OnUpdateState fun changeFruit( color: StateValue<Color>, name: StateValue<String>, @Param newColor: Color,

    @Param newName: String) { color.set(newColor) name.set(newName) } ... ... RootComponent.changeFruit(c, Color.BLUE, "Plum")
  112. None
  113. None
  114. What if State is not tied to rendering?

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

    { return Column.create(c) // ... .build() }
  116. @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() }
  117. @State(canUpdateLazily = true) step: Int RootComponent.lazyUpdateStep(c, value)

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

  119. Can I use State for caching?

  120. @OnCalculateCachedValue(name = "heavyConfig") fun onCalculateHeavyConfig(@Prop prop: Long): HeavyConfig { return

    calculateHeavyConfig(prop) } @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop prop: Long, @CachedValue heavyConfig: HeavyConfig @State state: Boolean): Component { return ... }
  121. @OnCalculateCachedValue(name = "heavyConfig") fun onCalculateHeavyConfig(@Prop prop: Long): HeavyConfig { return

    calculateHeavyConfig(prop) } @OnCreateLayout fun onCreateLayout( c: ComponentContext, @Prop prop: Long, @CachedValue heavyConfig: HeavyConfig @State state: Boolean): Component { return ... }
  122. GOTCHAS

  123. GOTCHAS Batch your State updates

  124. GOTCHAS Batch your State updates Use lazy State updates where

    possible
  125. GOTCHAS Batch your State updates Use lazy State updates where

    possible Use CachedValues for heavy lifting
  126. ANIMATE ALL THE THINGS

  127. @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() }
  128. @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() }
  129. @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) }
  130. @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) }
  131. @OnCreateTransition fun.onCreateTransition(c: ComponentContext): Transition { return.Transition.create("button") .animate(AnimatedProperties.X) }

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

  133. ANIMATIONS

  134. ANIMATIONS onCreateTransition is called on every onCreateLayout

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

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

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

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

    Transition? { return if (shouldAnimate(prop, state)) Transition.allLayout() else null }
  139. @OnUpdateState fun updateAlignment(alignToEnd: StateValue<Boolean>) { alignToEnd.set(!alignToEnd.get()) }

  140. @OnUpdateStateWithTransition fun updateAlignment(alignToEnd: StateValue<Boolean>): Transition { alignToEnd.set(!alignToEnd.get()) return Transition.create("button") .animate(AnimatedProperties.X)

    }
  141. GOTCHAS

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

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

    use @OnUpdateStateWithTransition if animating only State changes
  144. GRADUAL ADOPTION

  145. GRADUAL ADOPTION

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

    step by step:
  147. 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
  148. 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
  149. ADVANCED CONFIGURATION

  150. COMPONENTTREE BUILDER

  151. COMPONENTTREE BUILDER Set of advanced settings val tree = ComponentTree.create(c)

    // ... .build() lithoView.componentTree = tree
  152. COMPONENTTREE BUILDER Set of advanced settings • LayoutHandler val tree

    = ComponentTree.create(c) .layoutThreadHandler(lithoHandler) .build() lithoView.componentTree = tree
  153. COMPONENTTREE BUILDER Set of advanced settings • LayoutHandler • Reconciliation

    val tree = ComponentTree.create(c) .isReconciliationEnabled(true) .build() lithoView.componentTree = tree
  154. SOMETHING DOESN'T WORK!

  155. TOOLS

  156. TOOLS Yoga Playground

  157. yogalayout.com/playground

  158. TOOLS Yoga Playground Flipper + Layout plugin

  159. fbflipper.com

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

    (Beta)
  161. None
  162. github.com/facebook/litho/tree/master/litho-intellij-plugin

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

    (Beta) Lithography Sample app
  164. None
  165. RESOURCES Official Docs: fblitho.com Intro to Litho: youtu.be/uzCK4Vnme7o Lithography Sample

    app: github.com/facebook/litho/tree/master/sample Litho Codelabs (early preview): github.com/facebook/litho/tree/master/codelabs Dive into Litho Threading Model: youtu.be/78BUH1LZaZ4
  166. THANK YOU Sergey Ryabov @colriot