Skip to content

Introduce support for rendering across subcompositions.#104

Merged
zach-klippenstein merged 2 commits intomainfrom
zachklipp/subcomposition-support
Oct 14, 2020
Merged

Introduce support for rendering across subcompositions.#104
zach-klippenstein merged 2 commits intomainfrom
zachklipp/subcomposition-support

Conversation

@zach-klippenstein
Copy link
Copy Markdown
Collaborator

@zach-klippenstein zach-klippenstein commented Oct 14, 2020

tl;dr: The first commit in this PR adds tests for the current (lack of) subcomposition support. See the test changes in the second commit to see the changes in renderings.

Fixes #93.

Subcomposition is where a reference is created to a particular position in a
parent composition, and then that reference is used as the parent of a separate
composition. The subcomposition inherits ambients and recompose scopes from the
parent, but can be created at any point in the lifetime of the parent, and
disposed before the parent composition is disposed. From the kdoc:

A [CompositionReference] is an opaque type that is used to logically "link" two compositions
together. The [CompositionReference] instance represents a reference to the "parent" composition
in a specific position of that composition's tree, and the instance can then be given to a new
"child" composition. This reference ensures that invalidations and ambients flow logically
through the two compositions as if they were not separate.

Subcomposition is used for a number of things:

  1. Compose children which have a data dependency on a property of the parent
       composition that is only available after the composition pass, e.g.
       WithConstraints (which can only compose its children during layout).
  2. Lazy composition, where the "current" actual children of a composable depend
       on some runtime state, and old/uncreated children should be not be composed
       when not needed, to save resources. LazyColumn does this.
  3. Linking compositions that need to be hosted in entirely separate windows together.
       Dialog uses this to make the dialog children act as children of the composable
       that invokes them, even though they're hosted in a separate window, with a
       Android view host.

Rendering subcomposition is tricky, because there's no explicit reference from the
parent CompositionReference to where the subcompositions' composables actually appear.
Fortunately, SubcomposeLayout is a helper composable which provides a convenient
wrapper around subcomposition for common use cases such as 1 and 2 above – basically
any time the subcomposition is actually a visual child of the parent composable, but
can only be created during the layout pass. SubcomposeLayout shows up as a pattern
in the slot tables' groups which Radiography detects and renders in a way that makes
the subcomposition look like regular children of the parent composable.

Non-SubcomposeLayout subcompositions, like the one from Dialog, are rendered
slightly more awkwardly. The subcomposition is shown as a child of the parent
layout node. In the case of Dialog, this is fine, since there's no actual layout
node in the parent composition which acts as a parent for the subcomposition. More
complex use cases may be rendered differently, e.g. if there's a layout node which
"hosts" the subcomposition, it will appear after the actual CompositionReference
in the slot table, and thus the subcomposition and its subtree will appear before
the layout node in the rendering.

Subcompositions are detected by looking for instances of CompositionReference in
the slot table. CompositionReference is an abstract class, but the only concrete
implementation currently in Compose contains references to all actual compositions
that use it as a parent. Reflection is used to pull the actual subcompositions out
of the parent reference, and then those compositions' slot tables are analyzed in
turn.

As an example, the nested composables used to render the Radiography logo in the compose sample app are now rendered correctly:
image

@zach-klippenstein zach-klippenstein force-pushed the zachklipp/subcomposition-support branch from 443f61e to d9180b2 Compare October 14, 2020 04:32
@zach-klippenstein zach-klippenstein force-pushed the zachklipp/subcomposition-support branch from d9180b2 to 708687f Compare October 14, 2020 15:12
Fixes #93.

Subcomposition is where a reference is created to a particular position in a
parent composition, and then that reference is used as the parent of a separate
composition. The subcomposition inherits ambients and recompose scopes from the
parent, but can be created at any point in the lifetime of the parent, and
disposed before the parent composition is disposed. From the kdoc:

```
A [CompositionReference] is an opaque type that is used to logically "link" two compositions
together. The [CompositionReference] instance represents a reference to the "parent" composition
in a specific position of that composition's tree, and the instance can then be given to a new
"child" composition. This reference ensures that invalidations and ambients flow logically
through the two compositions as if they were not separate.
```

Subcomposition is used for a number of things:

1. Compose children which have a data dependency on a property of the parent
   composition that is only available after the composition pass, e.g.
   `WithConstraints` (which can only compose its children during layout).
2. Lazy composition, where the "current" actual children of a composable depend
   on some runtime state, and old/uncreated children should be not be composed
   when not needed, to save resources. `LazyColumn` does this.
3. Linking compositions that need to be hosted in entirely separate windows together.
   `Dialog` uses this to make the dialog children act as children of the composable
   that invokes them, even though they're hosted in a separate window, with a
   Android view host.

Rendering subcomposition is tricky, because there's no explicit reference from the
parent `CompositionReference` to where the subcompositions' composables actually appear.
Fortunately, `SubcomposeLayout` is a helper composable which provides a convenient
wrapper around subcomposition for common use cases such as 1 and 2 above – basically
any time the subcomposition is actually a visual child of the parent composable, but
can only be created during the layout pass. `SubcomposeLayout` shows up as a pattern
in the slot tables' groups which Radiography detects and renders in a way that makes
the subcomposition look like regular children of the parent composable.

Non-`SubcomposeLayout` subcompositions, like the one from `Dialog`, are rendered
slightly more awkwardly. The subcomposition is shown as a child of the parent
layout node. In the case of `Dialog`, this is fine, since there's no actual layout
node in the parent composition which acts as a parent for the subcomposition. More
complex use cases may be rendered differently, e.g. if there's a layout node which
"hosts" the subcomposition, it will appear after the actual `CompositionReference`
in the slot table, and thus the subcomposition and its subtree will appear before
the layout node in the rendering.

Subcompositions are detected by looking for instances of `CompositionReference` in
the slot table. `CompositionReference` is an abstract class, but the only concrete
implementation currently in Compose contains references to all actual compositions
that use it as a parent. Reflection is used to pull the actual subcompositions out
of the parent reference, and then those compositions' slot tables are analyzed in
turn.
@zach-klippenstein zach-klippenstein force-pushed the zachklipp/subcomposition-support branch from 708687f to c9d3758 Compare October 14, 2020 19:13
@zach-klippenstein
Copy link
Copy Markdown
Collaborator Author

Tweaked the rendering a bit, so the children of each subcomposition is grouped under that particular subcomposition. For stuff like LazyColumn, this makes it clear which composables belong to individual list "items".

@zach-klippenstein zach-klippenstein merged commit 3ccbd3c into main Oct 14, 2020
@zach-klippenstein zach-klippenstein deleted the zachklipp/subcomposition-support branch October 14, 2020 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scanning stops at subcomposition boundaries

2 participants