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

Sergey Ryabov

December 07, 2019
Tweet

More Decks by Sergey Ryabov

Other Decks in Programming

Transcript

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

    ✦ State management ✦ Animations ✦ Interop ✦ Tools
  2. WHAT IS LITHO ✦ UI framework ✦ 4.5 years old

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

    ✦ Used in Facebook apps ✦ But not only... appbrain.com/stats/libraries/details/litho/litho
  4. fun f( title: String, subtitle: String ): UI { return

    Column.create() .child( Text.create() .text(title)) .child( Text.create() .text(subtitle)) .build() }
  5. @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. @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() } }
  10. @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() } }
  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() } } @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() } }
  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() } } @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() } }
  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() } } @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() } }
  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 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 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() } }
  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() } } 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())) } }
  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() } } 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())) } }
  23. HOW DOES IT ALL WORK 1. Create an Internal Tree

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

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

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

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

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

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

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

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

    // ... Column.create(c) .child( Text.create(c) .text(title)) .child( Text.create(c) .text(subtitle)) .build() } }
  36. 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() } }
  37. 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() } }
  38. 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() } }
  39. 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)
  40. 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)
  41. 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. 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
  43. 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)
  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 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 LayoutState
  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. 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
  52. 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
  53. BUILDING BLOCKS ✦ @LayoutSpec ✦ Row / Column ✦ Image

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

    Text Text ✦ @MountSpec ✦ @LayoutSpec ✦ Row / Column
  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. @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 } }
  57. @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 } }
  58. @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 } }
  59. public @interface MountSpec { int poolSize() default 3; boolean isPureRender()

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

    default false; } @MountSpec(poolSize = 30, isPureRender = true) class ImageSpec { @ShouldUpdate static boolean shouldUpdate(...) {} }
  61. 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
  62. 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
  63. 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
  64. 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
  65. (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
  66. (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
  67. GOTCHAS ✦ Use isPureRender / @ShouldUpdate to reuse previous layout

    results for @MountSpecs ✦ Tweak MountPools according to you requirements
  68. @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() }
  69. @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() }
  70. @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() }
  71. 
 @OnUpdateState fun increment(count: StateValue<Int>) { count.set(count.get() + 1) }


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


    @OnEvent(ClickEvent::class) fun.onIncrement(c: ComponentContext) { RootComponent.increment(c) }
  74. @OnUpdateState fun changeColor(color: StateValue<Color>, @Param newColor: Color) { color.set(newColor) }

    @OnUpdateState fun changeName(name: StateValue<String>, @Param newName: String) { name.set(newName) }
  75. @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")
  76. @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")
  77. @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")
  78. @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() }
  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!") .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!") .alignSelf(align) .clickHandler(RootComponent.onClick(c)) ) .build() }
  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() }
  82. @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) }
  83. @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) }
  84. ANIMATIONS onCreateTransition is called on every onCreateLayout Even if you

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

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

    Transition? { return if (canAnimate(prop, state)) Transition.allLayout() else null }
  87. @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() }
  88. 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
  89. 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
  90. WHAT IS LITHO ✦ Asynchronous layout ✦ Declarative API ✦

    Flatter view hierarchies ✦ Fine-grained recycling
  91. 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
  92. Column { +Text(text = "Hello, Kotlin World!", textSize = 20.sp)

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

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

    { +Text(text = "Hello, Kotlin World!", textSize = 20.sp) +Text( text = "with ❤ from London", textStyle = Typeface.ITALIC) } } }
  95. 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) } } }
  96. 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) } } } }
  97. 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) } } } } } }