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

Unravelling materialization process in Jetpack ...

Unravelling materialization process in Jetpack Compose

This session goes over the process used by ComposeUi, in constructing the actual ui.

Gibson Ruitiari

November 26, 2022
Tweet

Transcript

  1. 👋 About me 📛 Gibson Ruitiari 🎒 Law Student 💻

    Hobbyist Android + Kotlin developer 💁‍♂️ Manga+comics 🐙 https://github.com/GibsonRuitiari
  2. setContent { // houses ui- building blocks such as Box,

    Lazy Layout, Text etc Text(text=“hello world”) }
  3. @Composable fun HelloWorld():Unit Execution of this composable Leads to scheduling

    of changes Changes involve inserting, moving, replacing ui Nodes within the layout tree
  4. All Layouts emit the same type of UI-Node : Layout

    Node Compose Ui-Building blocks, such as Box, Lazy Column, Text, Image etc are modelled as Layouts
  5. Layout Node represents a UI-Block All Layouts emit the same

    type of UI-Node : Layout Node Compose Ui-Building blocks, such as Box, Lazy Column, Text, Image etc are modelled as Layouts
  6. Layout Node is also responsible for inserting, removing, reordering its

    children Layout Node contains a list of all its children
  7. Multiple connected Layout Nodes make up what we call the

    Ui Tree Layout Node is also responsible for inserting, removing, reordering its children Layout Node contains a list of all its children
  8. Layout Node can only have one parent, which is also

    a Layout Node Multiple connected Layout Nodes make up what we call the Ui Tree Layout Node is also responsible for inserting, removing, reordering its children Layout Node contains a list of all its children
  9. *Each parent can only have one owner Layout Node can

    only have one parent, which is also a Layout Node Multiple connected Layout Nodes make up what we call the Ui Tree Layout Node is also responsible for inserting, removing, reordering its children Layout Node contains a list of all its children
  10. @Composable inline fun Layout( content: @Composable @UiComposable () -> Unit,

    modifier: Modifier = Modifier, measurePolicy: MeasurePolicy ) { val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current val viewConfiguration = LocalViewConfiguration.current
  11. // emits the ReusableComposeNode ReusableComposeNode<ComposeUiNode, Applier<Any>>( factory = { LayoutNode()

    }, update = { set(measurePolicy, ComposeUiNode.SetMeasurePolicy) set(density, ComposeUiNode.SetDensity) set(layoutDirection, ComposeUiNode.SetLayoutDirection) set(viewConfiguration, ComposeUiNode.SetViewConfiguration) }, skippableUpdate = materializerOf(modifier), content = content ) }
  12. • When the Key changes, the content of the Reusable

    compose node is updated • These UI-Nodes have keys that identify them Why ReusableComposeNode
  13. • Changes happening to this node leads to the particular

    node being updated • When the Key changes, the content of the Reusable compose node is updated • These UI-Nodes have keys that identify them Why ReusableComposeNode
  14. • The Compiler does not discard/create a new node, instead

    it updates it • Changes happening to this node leads to the particular node being updated • When the Key changes, the content of the Reusable compose node is updated • These UI-Nodes have keys that identify them Why ReusableComposeNode
  15. For Ui-Node to be re-usable it must have update and

    set methods in the emit call update = { set(measurePolicy, ComposeUiNode.SetMeasurePolicy) set(density, ComposeUiNode.SetDensity) set(layoutDirection, ComposeUiNode.SetLayoutDirection) set(viewConfiguration, ComposeUiNode.SetViewConfiguration) }, This is why all Layout Nodes are modelled as ReusableComposeNode
  16. update = { set(measurePolicy, ComposeUiNode.SetMeasurePolicy) set(density, ComposeUiNode.SetDensity) set(layoutDirection, ComposeUiNode.SetLayoutDirection) set(viewConfiguration,

    ComposeUiNode.SetViewConfiguration) }, Properties are set during the initial composition Updated during re-compositions if The properties have changed
  17. • It is the work of the client ui library,

    as in this case, ComposeUi [Android] to change the UI-Nodes in the Tree to the actual UI, that can be seen by the User
  18. • This process is what is referred to as Materialization

    • It is the work of the client ui library, as in this case, ComposeUi [Android] to change the UI-Nodes in the Tree to the actual UI, that can be seen by the User
  19. • Applier is an abstraction used by the runtime to

    materialize the Ui Nodes • Base Implementation of the Applier is the Abstract Applier, that is used by Client Libraries to make their own Applier implantation
  20. • During materialization, the Applier traverses through the whole tree

    • Visited nodes are stored in a stack • The reference of the current visited Ui-Node is held by the Abstract Applier to know which node it should perform operations on
  21. The Applier performs operations such as updating the Node’s content,

    on the inserted node When a node is visited, the node is pushed/inserted into the stack
  22. The Applier performs operations such as updating the Node’s content,

    on the inserted node Once the operations are done, the node is popped out of the stack, and the Applier moves to the next node in the tree When a node is visited, the node is pushed/inserted into the stack
  23. Column Loading state changes Applier transverses the tree by visiting

    the Column Layout Node Inserts /Deletes the 1st or 2nd Text() depending on the Loading State Current visited Node Text Text
  24. The popping and insertion of nodes into the Stack are

    carried out by the Abstract Applier
  25. Existing implementations therefore do not need to implement their own

    navigation logic The popping and insertion of nodes into the Stack are carried out by the Abstract Applier
  26. Existing implementations therefore do not need to implement their own

    navigation logic Implementation of the AbstractApplier in ComposeUi [Android client] is the UiApplier The popping and insertion of nodes into the Stack are carried out by the Abstract Applier
  27. The UiApplier is responsible for materializing LayoutNodes in the tree

    Existing implementations therefore do not need to implement their own navigation logic Implementation of the AbstractApplier in ComposeUi [Android client] is the UiApplier The popping and insertion of nodes into the Stack are carried out by the Abstract Applier
  28. Bottom up Approach Nodes are inserted bottom –up, to avoid

    duplicate notification of ancestral nodes Row
  29. Nesting of composables in Jetpack compose is not a problem

    because of the bottom-up approach used By the Ui Applier
  30. Therefore, unlike when you are using xml, in Jetpack compose

    you do not need to flatten your UI hierarchies Nesting of composables in Jetpack compose is not a problem because of the bottom-up approach used By the Ui Applier
  31. internal class UiApplier( root: LayoutNode ):AbstractApplier(root) { override fun insertTopDown(index:

    Int, instance: LayoutNode) { // Ignored. Android uses bottom up strategy } override fun insertBottomUp(index: Int, instance: LayoutNode){ current.insertAt(index, instance) } override fun remove(index: Int, count: Int) { current.removeAt(index, count) }
  32. Top Down Approach Building of the Ui tree starts from

    top to down, This approach is used by Vector Appliers Row Text Text
  33. Multiple notifications of ancestral nodes can cause performance issues, hence

    why This strategy is only used by Vector Applier In top-down approach, during node insertion, all ancestors are notified
  34. We know how to build the Ui Tree but how

    do we exactly do we draw the actual Ui?
  35. Existing implementations of Jetpack compose, thus must have an integration

    point Owner (AndroidComposeView) Jetpack compose is just a UI framework, independent of its implementations.
  36. All the Android related properties such as context, are provided

    to ComposeUi by the AndroidComposeView The Android compose view connects/bridges the Layout Nodes with Android Views
  37. AndroidComposeView is actually a view group All the Android related

    properties such as context, are provided to ComposeUi by the AndroidComposeView The Android compose view connects/bridges the Layout Nodes with Android Views
  38. Whenever a node is added, updated or removed from the

    tree, the Android compose view Invalidates itself
  39. After invalidation, changes are reflected onto the screen during the

    next draw pass Whenever a node is added, updated or removed from the tree, the Android compose view Invalidates itself
  40. Example: Node insertion internal fun insertAt(index: Int, instance: LayoutNode) {

    check(instance._foldedParent == null) { "Cannot insert $instance because it already has a parent." + " This tree: " + debugTreeToString() + " Other tree: " + instance._foldedParent?.debugTreeToString() } check(instance.owner == null) { "Cannot insert $instance because it already has an owner." + " This tree: " + debugTreeToString() + " Other tree: " + instance.debugTreeToString() } instance._foldedParent = this _foldedChildren.add(index, instance);onZSortedChildrenInvalidated() /** Other code… omitted by me for brevity☺ */
  41. New Node List<LayoutNodes> List of children maintained by parent node

    Get’s added to the list List is sorted according to the Z index of the Children present in the list Node is attached the same owner as the parent Owner is requested by the parent to re-measuring of the new node and the parent View is flagged as “dirty”
  42. *Since the parent houses the newly inserted node, it has

    to request the owner [AndroidComposeView], to remeasure it together with the its child [the newly inserted node] *Process for removing/inserting Layout Nodes follows a similar procedure *Layout Nodes are drawn according to their zIndex