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

Sergey Ryabov

August 27, 2019
Tweet

More Decks by Sergey Ryabov

Other Decks in Programming

Transcript

  1. @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() } }
  2. @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() } }
  3. @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() } }
  4. @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() } }
  5. @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() } }
  6. @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() } }
  7. @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() } }
  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() } } 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())) } }
  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() } } 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())) } }
  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() } } 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())) } }
  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() } } 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())) } }
  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() } } 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())) } }
  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() } } @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() } }
  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() } } @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() } }
  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() } } @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() } }
  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() } } @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() } }
  19. HOW DOES IT ALL WORK 1. Create an Internal Tree

    2. Create LayoutState 3. Mount LayoutState
  20. @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() } }
  21. @LayoutSpec object ListItemWithImageSpec { // ... Row.create(c) .child( Image.create(c) .drawable(image))

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

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

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

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

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

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

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

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

    ... Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  32. 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() } }
  33. 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() } }
  34. 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() } }
  35. 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() } }
  36. 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)
  37. 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)
  38. 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)
  39. 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
  40. 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)
  41. 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)
  42. 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)
  43. 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)
  44. 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)
  45. 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)
  46. 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)
  47. 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
  48. 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
  49. 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
  50. MOUNT LAYOUTSTATE The only step that always happens on UI

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

    able LayoutState Row Image Column Text Text
  52. @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 } // ... }
  53. @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 } // ... }
  54. @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 } // ... }
  55. @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 } // ... }
  56. public @interface MountSpec { int poolSize() default 3; boolean isPureRender()

    default false; } @MountSpec(poolSize = 30, isPureRender = true) class ImageSpec {
  57. 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
  58. 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
  59. 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
  60. 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
  61. (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
  62. (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
  63. GOTCHAS Use isPureRender / @ShouldUpdate to reuse previous layout results

    Tweak MountPools according to you requirements
  64. @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() }
  65. @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() }
  66. 
 @OnUpdateState fun increment(count: StateValue<Int>) { count.set(count.get() + 1) }


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


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


    RootComponent.increment(c) 
 RootComponent.incrementSync(c)
  70. @OnUpdateState fun changeColor(color: StateValue<Color>, @Param newColor: Color) { color.set(newColor) }

    @OnUpdateState fun changeName(name: StateValue<String>, @Param newName: String) { name.set(newName) }
  71. @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")
  72. @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")
  73. @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")
  74. @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() }
  75. @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 ... }
  76. @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 ... }
  77. GOTCHAS Batch your State updates Use lazy State updates where

    possible Use CachedValues for heavy lifting
  78. @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() }
  79. @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() }
  80. @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) }
  81. @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) }
  82. ANIMATIONS onCreateTransition is called on every onCreateLayout Even if you

    don't need to animate that specific UI update More granular control?
  83. @OnCreateTransition fun onCreateTransition(c: ComponentContext, @Prop prop: Diff<String>, @State state: Diff<Boolean>):

    Transition? { return if (shouldAnimate(prop, state)) Transition.allLayout() else null }
  84. GOTCHAS Use Diff<T> for more granular control over transitions Can

    use @OnUpdateStateWithTransition if animating only State changes
  85. 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
  86. 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
  87. COMPONENTTREE BUILDER Set of advanced settings • LayoutHandler val tree

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

    val tree = ComponentTree.create(c) .isReconciliationEnabled(true) .build() lithoView.componentTree = tree
  89. 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