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

Droidcon SF: What does Recomposition mean to your app?

Droidcon SF: What does Recomposition mean to your app?

PRESENTED AT:
Droidcon San Francisco 2022

https://www.sf.droidcon.com/speaker/aida-issayeva/what-does-recomposition-mean-to-your-app%3F

DATE:
Jun 3, 2022

TIME:
13:20 > 40 min

DESCRIPTION:
You’ve heard a lot that Jetpack Compose is a declarative UI toolkit and it recomposes only the components that changed. But what does it exactly mean? How does it apply not only in the scale of one composable function but the entire app? Well, it turns out there is more than meets the eye when it comes to recomposition, and in this talk, we will deep dive into its benefits, costs, and gotchas. You’ll learn some practical tips to efficiently build functions in Jetpack Compose.

MORE TALKS & ARTICLES FROM ME: https://cupsofcode.com/talks/

Aida Issayeva

June 03, 2022
Tweet

More Decks by Aida Issayeva

Other Decks in Programming

Transcript

  1. What does
    Recomposition mean to
    your app?
    Aida Issayeva

    View full-size slide

  2. About me
    🤖 Android GDE
    󰠁 Sr. Software Engineer
    󰠅 Instructor
    🐦 @aida_isay
    🌐 cupsofcode.com
    📽 @aida_isay

    View full-size slide

  3. TODAY’S
    AGENDA
    02
    What is Recomposition?
    03
    Smart Recomposition
    04
    Tools to detect & debug
    05
    Practical tips
    01
    Deep into the foundation
    06
    Jetpack Compose Paradigm

    View full-size slide

  4. Jetpack Compose paradigm
    UI widgets are functions
    They are stateless
    Different arguments, passed to
    the functions, trigger updates

    View full-size slide

  5. SlotTable
    Android OS
    Composer
    Applier
    UI Node Tree
    State Snapshot System
    Change List
    Applier

    View full-size slide

  6. Deep into the
    foundation

    View full-size slide

  7. 02 Deep into the foundation
    Compose Phases
    1
    Composition
    Calls composable functions and
    creates a description of the UI
    2
    Layout
    Measures the layout elements
    and their children and places
    them on the screen
    3
    Drawing
    Draws the UI elements to the
    screen

    View full-size slide

  8. 02 Deep into the foundation
    Compose Layers
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing

    View full-size slide

  9. 02 Deep into the foundation
    Compose Layers
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing

    View full-size slide

  10. From Android OS:
    ● Context
    ● Configuration
    ● Lifecycle
    ● Keyboard state
    ● SavedStateInstance
    ● etc
    CompositionLocals
    02 Deep into the foundation
    Entrance to Jetpack Compose
    class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    ...
    setContent {
    Card()
    }
    }
    }

    View full-size slide

  11. 02 Deep into the foundation
    Compose Layers
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing

    View full-size slide

  12. 02 Deep into the foundation
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing
    Composition:
    1. SlotTable
    2. Composer
    3. Changes List
    Applier:
    UI materializer
    Recomposer:
    1. State Snapshot System
    2. Compositions - list

    View full-size slide

  13. 02 Deep into the foundation
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing
    Composition:
    1. SlotTable
    2. Composer
    3. Changes List
    Applier:
    UI materializer
    Recomposer:
    1. State Snapshot System
    2. Compositions - list

    View full-size slide

  14. is the scheduler for performing recomposition and
    applying updates to one or more Compositions.
    Recomposer

    View full-size slide

  15. 02 Deep into the foundation
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing
    Composition:
    1. SlotTable
    2. Composer
    3. Changes List
    Applier:
    UI materializer
    Recomposer:
    1. State Snapshot System
    2. Compositions - list
    WindowRecomposer:
    LifecycleAware recomposer

    View full-size slide

  16. Android Lifecycle & Compose
    viewTreeLifecycleOwner .lifecycle.addObserver(
    object : LifecycleEventObserver {
    override fun onStateChanged (lifecycleOwner: LifecycleOwner , event:
    Lifecycle .Event) {
    val self = this
    when (event) {
    Lifecycle.Event.ON_CREATE ->
    runRecomposeScope .launch(start = CoroutineStart .UNDISPATCHED) {
    try {
    recomposer.runRecomposeAndApplyChanges()
    } finally {
    lifecycleOwner .lifecycle.removeObserver( self)
    }
    }
    Lifecycle .Event.ON_START -> pausableClock ?.resume()
    Lifecycle .Event.ON_STOP -> pausableClock ?.pause()
    Lifecycle.Event.ON_DESTROY -> {
    recomposer.cancel()
    }
    Lifecycle .Event.ON_PAUSE -> {
    // Nothing
    }
    Lifecycle .Event.ON_RESUME -> {
    // Nothing
    }
    Lifecycle .Event.ON_ANY -> {
    // Nothing
    }
    }
    }
    }
    )
    02 Deep into the foundation
    Android OS
    State Snapshot
    System

    View full-size slide

  17. State(“Android”)
    State Snapshot System
    @Composable
    fun StateSnapshotSystem() {
    val name = remember {
    mutableStateOf(Android)
    }
    val isImportant = remember {
    mutableStateOf(false)
    }
    Column {
    Checkbox(checked = isImportant.value,
    onCheckedChange = {
    isImportant.value = !isImportant.value
    })
    Text(text = name.value,
    modifier = Modifier.clickable {
    name.value = swapName(name.value)
    }
    )
    }
    }
    02 Deep into the foundation
    State(false)
    State(“Android”)
    State(true)
    State(false)

    View full-size slide

  18. 02 Deep into the foundation
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing
    Composition:
    1. SlotTable
    2. Composer
    3. Changes List
    Applier:
    UI materializer
    Recomposer:
    1. State Snapshot System
    2. Compositions - list

    View full-size slide

  19. Composition
    is a process of executing
    composable functions

    View full-size slide

  20. Composer & SlotTable
    @Composable
    fun Card() {
    Column {
    Text(
    text = "Compose",
    fontSize = 24.sp,
    fontWeight = FontWeight.Bold
    )
    Button(onClick = {
    }) {
    Text(text = "Click me")
    }
    }
    }
    02 Deep into the foundation

    View full-size slide

  21. Composer & SlotTable
    @Composable
    fun Card() {
    Column {
    Text(
    text = "Compose",
    fontSize = 24.sp,
    fontWeight = FontWeight.Bold
    )
    Button(onClick = {
    }) {
    Text(text = "Click me")
    }
    }
    }
    02 Deep into the foundation
    …..

    View full-size slide

  22. Composer & SlotTable
    @Composable
    fun Card() {
    Column {
    Text(
    text = "Compose",
    fontSize = 24.sp,
    fontWeight = FontWeight.Bold
    )
    Button(onClick = {
    }) {
    Text(text = "Click me")
    }
    }
    }
    02 Deep into the foundation
    Group(123)
    …..
    C
    A
    R
    D

    View full-size slide

  23. Composer & SlotTable
    @Composable
    fun Card() {
    Column {
    Text(
    text = "Compose",
    fontSize = 24.sp,
    fontWeight = FontWeight.Bold
    )
    Button(onClick = {
    }) {
    Text(text = "Click me")
    }
    }
    }
    02 Deep into the foundation
    Group(123)
    …..
    Group(456)
    C
    A
    R
    D
    C
    O
    L
    U
    M
    N

    View full-size slide

  24. Composer & SlotTable
    @Composable
    fun Card() {
    Column {
    Text(
    text = "Compose",
    fontSize = 24.sp,
    fontWeight = FontWeight.Bold
    )
    Button(onClick = {
    }) {
    Text(text = "Click me")
    }
    }
    }
    02 Deep into the foundation
    Group(123)
    …..
    Group(456)
    “Compose”
    C
    A
    R
    D
    C
    O
    L
    U
    M
    N

    View full-size slide

  25. Composer & SlotTable
    @Composable
    fun Card() {
    Column {
    Text(
    text = "Compose",
    fontSize = 24.sp,
    fontWeight = FontWeight.Bold
    )
    Button(onClick = {
    }) {
    Text(text = "Click me")
    }
    }
    }
    02 Deep into the foundation
    Group(123)
    …..
    Group(456)
    “Compose”
    Group(789)
    C
    A
    R
    D
    C
    O
    L
    U
    M
    N
    B
    U
    T
    T
    O
    N

    View full-size slide

  26. Composer & SlotTable
    @Composable
    fun Card() {
    Column {
    Text(
    text = "Compose",
    fontSize = 24.sp,
    fontWeight = FontWeight.Bold
    )
    Button(onClick = {
    }) {
    Text(text = "Click me")
    }
    }
    }
    02 Deep into the foundation
    Group(123)
    …..
    Group(456)
    “Compose”
    Group(789)
    C
    A
    R
    D
    C
    O
    L
    U
    M
    N
    B
    U
    T
    T
    O
    N
    { ... }

    View full-size slide

  27. Composer & SlotTable
    @Composable
    fun Card() {
    Column {
    Text(
    text = "Compose",
    fontSize = 24.sp,
    fontWeight = FontWeight.Bold
    )
    Button(onClick = {
    }) {
    Text(text = "Click me")
    }
    }
    }
    02 Deep into the foundation
    Group(123)
    …..
    Group(456)
    “Compose”
    Group(789)
    “Click me”
    C
    A
    R
    D
    C
    O
    L
    U
    M
    N
    B
    U
    T
    T
    O
    N
    { ... }

    View full-size slide

  28. 02 Deep into the foundation
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing
    Composition:
    1. SlotTable
    2. Composer
    3. Changes List
    Applier:
    UI materializer
    Recomposer:
    1. State Snapshot System
    2. Compositions - list

    View full-size slide

  29. Applier
    is an interface for materializing
    UI tree

    View full-size slide

  30. 02 Deep into the foundation
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing
    Composition:
    1. SlotTable
    2. Composer
    3. Changes List
    Applier:
    UI materializer
    Recomposer:
    1. State Snapshot System
    2. Compositions - list
    UiApplier:
    Applier implementation
    WindowRecomposer:
    LifecycleAware recomposer

    View full-size slide

  31. Layout phase steps
    02 Deep into the foundation
    Column
    LayoutNode
    Button
    LayoutNode
    Text
    LayoutNode
    Text
    LayoutNode
    Root Node
    1. Add child nodes bottom-up to the
    root node
    2. Measure the child nodes
    3. Define the size of a parent node
    4. Place the child nodes on screen
    C
    O
    N
    S
    T
    R
    A
    I
    N
    T
    S
    M
    E
    A
    S
    U
    R
    E
    D
    S
    I
    Z
    E

    View full-size slide

  32. 02 Deep into the foundation
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing
    Composition:
    1. SlotTable
    2. Composer
    3. Changes List
    Applier:
    UI materializer
    Recomposer:
    1. State Snapshot System
    2. Compositions - list
    UiApplier:
    Applier implementation
    WindowRecomposer:
    LifecycleAware recomposer

    View full-size slide

  33. Final result
    02 Deep into the foundation

    View full-size slide

  34. What is
    recomposition?

    View full-size slide

  35. Recomposition
    is a re-execution of composable functions in
    response to state changes

    View full-size slide

  36. 03 What is recomposition
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing
    Composition:
    1. SlotTable
    2. Composer
    3. Changes List
    Applier:
    UI materializer
    Recomposer:
    1. State Snapshot System
    2. Compositions - list
    UiApplier:
    Applier implementation
    WindowRecomposer:
    LifecycleAware recomposer

    View full-size slide

  37. Recomposition in SlotTable:
    example 1
    @Composable
    fun Card() {
    val counter = remember {
    mutableStateOf(0)
    }
    Column {
    Text(text = "Compose")
    Button(onClick = {
    counter.value++
    }) {
    Text(text = "Click me $counter")
    }
    }
    }
    03 What is recomposition
    Group(456)
    …..
    Group(789)
    “Compose”
    Group(101)
    “Click me 0”
    C
    A
    R
    D
    C
    O
    L
    U
    M
    N
    B
    U
    T
    T
    O
    N
    { ... }
    Group(123)
    State(0)
    R
    E
    M
    E
    M
    B
    E
    R

    View full-size slide

  38. Recomposition in SlotTable:
    example 1
    @Composable
    fun Card() {
    val counter = remember {
    mutableStateOf(0)
    }
    Column {
    Text(text = "Compose")
    Button(onClick = {
    counter.value++
    }) {
    Text(text = "Click me $counter")
    }
    }
    }
    03 What is recomposition
    Group(456)
    …..
    Group(789)
    “Compose”
    Group(101)
    “Click me 0”
    C
    A
    R
    D
    C
    O
    L
    U
    M
    N
    B
    U
    T
    T
    O
    N
    { ... }
    Group(123)
    State(0)
    R
    E
    M
    E
    M
    B
    E
    R

    View full-size slide

  39. Recomposition in SlotTable:
    example 1
    @Composable
    fun Card() {
    val counter = remember {
    mutableStateOf(0)
    }
    Column {
    Text(text = "Compose")
    Button(onClick = {
    counter.value++
    }) {
    Text(text = "Click me $counter")
    }
    }
    }
    03 What is recomposition
    Group(456)
    …..
    Group(789)
    “Compose”
    Group(101)
    “Click me 1”
    C
    A
    R
    D
    C
    O
    L
    U
    M
    N
    B
    U
    T
    T
    O
    N
    { ... }
    Group(123)
    State(1)
    R
    E
    M
    E
    M
    B
    E
    R

    View full-size slide

  40. Recomposition in SlotTable:
    example 2
    @Composable
    fun Card() {
    val counter = remember {
    mutableStateOf(0)
    }
    Column {
    Text(text = "Compose $counter")
    Button(onClick = {
    counter.value++
    }) {
    Text(text = "Click me")
    }
    }
    }
    03 What is recomposition
    Group(456)
    …..
    Group(789)
    “Compose 0”
    Group(101)
    “Click me”
    C
    A
    R
    D
    C
    O
    L
    U
    M
    N
    B
    U
    T
    T
    O
    N
    { ... }
    Group(123)
    State(0)
    R
    E
    M
    E
    M
    B
    E
    R

    View full-size slide

  41. Recomposition in SlotTable:
    example 2
    @Composable
    fun Card() {
    val counter = remember {
    mutableStateOf(0)
    }
    Column {
    Text(text = "Compose $counter")
    Button(onClick = {
    counter.value++
    }) {
    Text(text = "Click me")
    }
    }
    }
    03 What is recomposition
    Group(456)
    …..
    Group(789)
    “Compose 1”
    Group(101)
    “Click me”
    C
    A
    R
    D
    C
    O
    L
    U
    M
    N
    B
    U
    T
    T
    O
    N
    { ... }
    Group(123)
    State(1)
    R
    E
    M
    E
    M
    B
    E
    R

    View full-size slide

  42. 03 What is recomposition
    Compose Phases
    1
    Composition
    Calls composable functions and
    creates a description of the UI
    2
    Layout
    Measures the layout elements
    and their children and places
    them on the screen
    3
    Drawing
    Draws the UI elements to the
    screen

    View full-size slide

  43. Smart
    recomposition

    View full-size slide

  44. Inputs are stable
    Smart recomposition is an intelligent way to skip the
    recomposition of composable functions when their

    View full-size slide

  45. Stable type contract rules:
    RULE 1
    The result of calls
    for two instances to
    `equals()` function
    will always be the
    same for the same
    two instances
    RULE 2
    When a public
    property of the type
    changes, Composition
    is notified
    RULE 3
    All public properties
    of the type are also
    stable

    View full-size slide

  46. Stable property types:
    Primitive types:
    Int, Boolean, Long, Char, Float, etc
    String
    Annotated with @Stable

    View full-size slide

  47. What about custom
    data types?
    04 Smart Recomposition

    View full-size slide

  48. Exceptions:
    1. Enum
    2. Enum Entry
    3. Interface
    4. Annotation
    5. Companion Object
    6. Inline
    7. Anonymous object
    8. Expect element
    9. Inner class
    04 Smart Recomposition
    Inferring Class stability
    ● visits each eligible class
    ● annotates with @StabilityInferred
    ● helps compiler to determine what
    to recompose

    View full-size slide

  49. 04 Smart Recomposition
    UI
    Compiler
    Runtime
    1
    Composition
    2
    Layout
    3
    Drawing
    Composition:
    1. SlotTable
    2. Composer
    3. Changes List
    Applier:
    UI materializer
    Recomposer:
    1. State Snapshot System
    2. Compositions - list
    UiApplier:
    Applier implementation
    WindowRecomposer:
    LifecycleAware recomposer

    View full-size slide

  50. Tools to
    detect & debug
    recomposition

    View full-size slide

  51. 1. Recompose Highlighter
    ➢ Google Play team introduced the
    modifier
    ➢ Check out the code snippet below.
    05 Tools to detect

    View full-size slide

  52. 2. Log Statements
    05 Tools to detect
    ➢ Ancient and the most reliable.
    ➢ Android Studio Electric Eel has an
    automatic support for it.
    ➢ Wrapped as a modifier function.
    Check out the code snippet below

    View full-size slide

  53. 3. Compose Compiler Metrics
    05 Tools to detect
    {
    "skippableComposables" :
    64,
    "restartableComposables" :
    76,
    "readonlyComposables" :
    0,
    "totalComposables" : 76
    }
    ➢ Full analysis
    ➢ Restartable & !skippable = ⛳
    ➢ Check out the code snippet below
    for integration details

    View full-size slide

  54. Practical tips to
    leverage smart
    recomposition

    View full-size slide

  55. 1. Break down composable
    functions
    /**
    * 👍 : only associated column with changes gets
    recomposed.
    */
    @Composable
    fun GoodGreetingsTip1() {
    Column {
    Greeting("Android", 1, Gray200)
    Greeting("Compose", 2, Brown200)
    }
    }
    @Composable
    fun Greeting(name: String, columnNumber: Int,
    background: Color) {
    Column {
    . . .
    }
    }
    06 Practical tips

    View full-size slide

  56. 2. Use the key composable
    /**
    * 👍 : the key is set, and when item in the middle is
    removed, only LazyColumn gets recomposed
    * and not its children
    */
    @Composable
    fun GoodGreetingsTip2(fruits: List) {
    LazyColumn(
    modifier = Modifier
    .recompositionCounter("lazyColumn")
    ) {
    items(
    items = fruits,
    key = { it.name }
    ) { fruit ->
    Item(fruit)
    }
    }
    }
    06 Practical tips

    View full-size slide

  57. 3. Read the state value at the
    lowest composable function
    /**
    * 👍 : only inner column and its children get
    recomposed, because the read happens
    * in one of the children.
    */
    @Composable
    fun GoodGreetingsTip3(cardInfo: () -> CardInfo) {
    Column {
    UpperRow()
    GoodRow(cardInfo = cardInfo)
    }
    }
    @Composable
    private fun GoodRow(cardInfo: () -> CardInfo) {
    Row {
    GoodInnerColumn(cardInfo = cardInfo)
    }
    }
    06 Practical tips

    View full-size slide

  58. 4. Use modifier lambdas
    /**
    * 👍: With the receiver lambda function
    (Modifier.offset{}) in place, this composable
    function doesn't get recomposed.
    */
    @Composable
    private fun GoodGreetingsTip4(yAxis: () -> Int) {
    Row(
    modifier = Modifier
    .offset {
    IntOffset(y = yAxis(), x = 0)
    },
    verticalAlignment = Alignment.Bottom
    ) {
    . . .
    }
    }
    06 Practical tips

    View full-size slide

  59. 5. Utilize `derivedStateOf()`
    /**
    * 👍 : Only BottomBar gets recomposed whenever
    hideBottomBar's calculated state changes
    */
    @Composable
    private fun GoodGreetingsTip5(list: List) {
    val listState = rememberLazyListState()
    val hideBottomBar = remember {
    derivedStateOf { listState.isScrollInProgress }
    }
    Scaffold(bottomBar = {
    AnimatedVisibility(visible = !hideBottomBar.value)
    {
    BottomAppBar(content = { BottomNavigationItems
    ()})
    }
    }, content = {
    LazyColumn(state = listState) {
    . . .
    }
    })
    06 Practical tips

    View full-size slide

  60. IMPORTANT 1
    Move any logic out
    of the composable
    functions to
    viewmodels,
    presenters, etc
    6. Tips worth re-repeating
    IMPORTANT 2
    Don’t create
    dependable
    composable functions
    on the result of other
    composables functions
    IMPORTANT 3
    Avoid any
    side-effects in the
    composable
    functions, because
    they can run in
    parallel

    View full-size slide

  61. Recap
    UI widgets are
    composable functions
    3 Compose Phases
    UI widgets go through are Composition,
    Layout, Draw
    Recomposition is an
    expected process
    3 Compose Layers
    Jetpack Compose uses to create
    composable functions: Compiler,
    Runtime, UI
    Practical tips
    should be used to avoid common
    recomposition pitfalls
    Smart recomposition
    should be leveraged as much as
    possible to teach Compiler

    View full-size slide

  62. Resources
    1. Jetpack Compose Internals
    2. Jetpack Compose source code
    3. Understanding Jetpack Compose — part 1 of 2
    4. Understanding Jetpack Compose — part 2 of 2
    5. Recomposition made easy
    6. RecomposeHighlighter code snippet
    7. Compose Compiler Metrics
    8. A historical introduction to the Compose reactive state
    model
    9. What is “donut-hole” skipping in Jetpack Compose?
    10. Compose performance
    11. Composable Metrics
    12. Thinking in Compose
    13. Jetpack Compose Phases
    14. Jetpack Compose Lifecycle
    Resources
    Recomposition tips
    code samples

    View full-size slide

  63. THANK YOU
    www.cupsofcode.com
    Code | Live | Grow
    @aida_isay

    View full-size slide