Is every recompose scope always the body of a `@Co...
# compose
m
Is every recompose scope always the body of a
@Composable
function that isn’t
inline
and returns
Unit
without any exception? Does it never correspond to a random set of statements or anything that’s not the body of a qualifying
@Composable
function?
Is each recompose scope a snapshot?
c
No to both questions. By default a
Unit
returning
@Compsoable
is but you can make a function non-restartable explicitly with an
@NonRestartableCompposable
annotation. This is useful for wrappers that don't themselves, read state.
m
@Chuck Jazdzewski [G] Thanks a lot for having a look at this question. Your insights are always deeply appreciated. Would it then be fair to say the following?
A ‘recompose scope’ is the body of a
@Composable
function that isn’t
inline
, that returns
Unit
, and that doesn’t opt out of it (eg via
@NonRestartableComposable
)
c
Yes. You may want to call out that non-inline composable lambdas have their own recompose scope which might not be obvious given that description.
🙏 1
m
• The compose compiler finds and transforms every
@Composable
at compile time
◦ A Kotlin compiler plugin; a compile time dependency that manipulates the IR
◦ A
Composer
output parameter injected and propagated down the call graph
◦ Unless
inline
or opted out of, the body wrapped if
Unit
in a ‘recompose scope’
• The compose runtime is a framework used by code generated by the compose compiler
◦ A
CompositionContext
is a tree of runtime services rooted at a
Recomposer
node
◦ A
Composition
is written to by a
Composer
on compose & in a context while active
◦ Composes in a snapshot; observers record reads & writes by recompose scopes
@Chuck Jazdzewski [G] Is everything in this abstract executive summary that I’ve put together fair? Have I got anything wrong? (Also pardon the ping, but cc @shikasd)
c
The above is mostly correct but not how I would phrase it. Some details that could be improved are, 1. The Kotlin compiler also produces errors and warnings which is part of the front-end. 2. The
Composer
is also an input parameter as that is where `CompositionLocal`s come in, for example. 3. Composable lambdas are also recompose scopes. It is technically captured by "all functions" but most people don't think of lambdas that way so I always call it out explicitly. 4. The
Composition
holds the state of the composition. It is not written to directly but contains the slot table which is. What you wrote is not technically incorrect but I would have phrased this differently. 5. Composition observes reads in recompose scopes and changes to the values read regardless of where the change occurs. While composition observes writes in composition, this is a part of observing changes in general.
m
@Chuck Jazdzewski [G] Thank you for the excellent feedback. Your insights have been more helpful than anything else. Also apologies for the late reply, I didn’t want to waste your time by running every little edit by you. I’ve also done more research and now made enough progress for it to be worth asking you to have another look at. > • The Compose compiler finds and transforms every
@Composable
at compile time > ◦ A Kotlin compiler plugin; a compile time dependency that manipulates the IR > ◦ A generated
Composer
parameter reads to and writes from a tree in a
Composition
> ◦ Unless
inline
or opted out of, the body mapped if
Unit
to a ‘recompose scope’ > • The Compose runtime is a framework used by code generated by the compose compiler > ◦ Libraries implement
Applier
to listen for changes to a tree in a
Composition
> ◦ Drives compositions in a
CompositionContext
tree rooted at a
Recomposer
node > ◦ Composes in a snapshot; observers record reads & writes by recompose scopes > • A
Composition
(‘parent’) can create and remember children of its
CompositionContext
> ◦ A
Composition
in one (‘subcomposition’) is not known to the parent. > ◦ A subcomposition is expected to be materialised as a subtree in the parent byproduct by its
Applier
. The mental model I’m trying to establish characterises a
Composition
as that which encapsulates a tree which is internally implemented by groups and slots. A
Composer
submits changes to it, which are made to the tree and forwarded to implementors of
Applier
. It’s to be noted that while a
CompositionImpl
drives one
Applier
, there’s no technical limit to how many an arbitrary implementation of
Composition
can drive. An
Applier
thus observes a tree in this sense, and it’s conventionally expected to derive a downstream tree from the changes it’s notified of. It’s also to be noted that what an
Applier
does with the changes is fundamentally up to it, and what it means for its byproduct to be equivalent to the
Composition
is decided by it also. In summary, for each
Composition
, there’s a context that drives it, and byproducts derived from it by its appliers. In the case of Android UI, the context is either a
Recomposer
or a child
CompositionImpl
, and the byproducts are materialised LayoutNode trees. I plan to elaborate on your points 1 and 5 outside this summary in side notes.
c
This is mostly correct. I might quibble with how things are described but that is more a style choice than a correctness issue. The essence is correct,
@Compsoable
functions are transforms function that transform the data to a tree and listen for changes in that data to make corresponding changes to the tree. For Compose UI, that tree is LayoutNodes.