Jorge Castillo Profile picture
May 8, 2021 16 tweets 5 min read Read on X
When thinking about Compose it's good to notice that Composables don't yield actual UI, but "emit" changes to the in-memory structure managed by the runtime (slot table) via the Composer.

That representation has to be interpreted later on to "materialize" UI from it 🧵 Image
An example of this is the Layout Composable, used to implement UI components. Layout uses ComposeNode to emit changes about how to create, update, index, and store the node on the table. That's why Composables return Unit 😲

cs.android.com/androidx/platf… ImageImage
Emitting these changes takes place via the compile time injected Composer, and it happens when the composable function is executed. That is during composition ⚙️
Layout belongs to compose-ui 🎨 That is code the developer writes. ComposeNode is part of the runtime, like the Composer and the slot table. See how node type is T in the runtime, so Compose can work with generic nodes to support other use cases. Image
Layout fixes generic type T to be ComposeUiNode, which is ultimately LayoutNode. In other words compose-ui decides what type of node to use Compose for 💡 Image
This separation between the in-memory representation of the graph and how it is interpreted allows to integrate with other libraries that use different types of nodes, making it possible to write libraries for desktop, web, or non UI related nodes.
The expectation is Composable functions are fast and restartable, so they simply schedule changes instead of building real UI. Composition traverses the 🌲 executing all Composable funcs to make them emit, ultimately filling up the table. It optimizes & prioritizes the process.
Recomposition happens multiple times and for different reasons, one of them being that the data being read by some elements on the tree has varied. That will make those functions to execute again (restart), and therefore emit again and update the table.
Once composition is done its time for materializing the changes from the table. The runtime delegates this to the "Applier", which is an interface implemented by compose-ui. It traverses the structure interpreting and materializing all nodes 🌲
“Materialize” is the verb used in Compose internals to refer to the action of interpreting the tree of changes to finally yield whatever output we are using Compose for. That is UI in the case of Android. The runtime is agnostic of the Applier's implementation.
The UiApplier implementation for Android delegates all actions to insert, move, remove or replace nodes to the node itself, a LayoutNode. LayoutNode is part of the UI library and provides the implementation details on how to materialize itself ✏️

cs.android.com/androidx/platf…
This is where setContent becomes relevant. It adds an AndroidComposeView to the top level window decor view which draws all the Compose LayoutNodes to its own canvas. It sets itself as the owner of all the nodes, which is how it connects them with the View system 🔗
Here is where the relevant Android stuff like config, Context, LifecycleOwner, savedStateRegistryOwner, saveableStateRegistry & the actual owner view is linked and provided down the Composable tree via a CompositionLocalProvider, so you can access those things while coding 👩‍💻
The ultimate result of applying all the changes is that LayoutNodes are drawn to the AndroidComposeView Canvas whenever it receives the dispatchDraw() order, given it is an Android ViewGroup. ✅
Small disclaimer: If you read this 🧵 keep in mind all links are pointing to current indexed version in cs.android.com and that will likely vary over time since it’s all implementation details. That said, the overall spirit and concepts will remain.
I recommend reading this other thread as a continuation of this one. It clarifies the difference between the change list and the slot table. I was not able to dive that deep yet. Thanks @chuckjaz 👏

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Jorge Castillo

Jorge Castillo Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @JorgeCastilloPr

May 12
In Jetpack Compose, Composable functions build up the Composition tree when they execute for the first time, and then update it on every recomposition.

Here is a thread about Composition and node types in Compose. It covers both Compose UI and the Compose runtime 🧵 Image
Here is a diagram of how recomposition (meaning re-executing Composable functions) updates the node tree representation (Composition). Image
The way this really happens is by emitting a list of changes for the node tree whenever a Composable function executes. Those changes are packed together as deferred lambdas, and applied in a decoupled phase when the composition or recomposition process is complete 🤯👇 Image
Read 8 tweets
May 15, 2022
Mosaic by @JakeWharton is a nice case study for how to create a client library for the Jetpack Compose compiler and runtime. Some pointers on where to find the key pieces in Mosaic, if you are interested 🧵

github.com/JakeWharton/mo… Image
Any client library needs to define its own nodes and teach the runtime how to materialize them (provide an Applier). I.e: Teach it how to build and update the tree.

Here are the Mosaic nodes and the Applier implementation 👇 (2/*)

github.com/JakeWharton/mo…
Nodes know how to attach, measure, layout, & render themselves, so the Applier can simply delegate the actions of adding/removing/moving nodes to them.

This lets the runtime trigger those actions when required without knowing about implementation details of the platform 👍 (3/*)
Read 9 tweets
Apr 3, 2022
Some people seems a bit confused about when to use `composed` to write a custom Modifier in Jetpack Compose, and when to use the `then` extension function instead. Well, they are actually very different. Let me expand on this just a bit 🧵
A "standard" modifier is written using the `Modifier.then` function over the modifier object, or a previous modifier instance. All the modifiers we use daily are written like this, making use of an extension function for ergonomics.
To write a modifier, we must pick the correct type. E.g: LayoutModifier if we want to affect measuring and layout. ParentDataModifier, if we need to provide data to a parent during measuring and positioning. DrawModifier, if we need to draw into the Layout. There are many others.
Read 8 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us!

:(