Zach Klippenstein (he/him) [MOD]
05/13/2020, 2:56 AMCompositionReference
from a composable function, pass it through to a FrameLayout
hosted inside the composition, and have that legacy view pass that reference through to a composable hosted inside itself? I.e. so that any ambients in effect where the legacy view is added to the composition get propagated to the leaf composable inside the legacy view.
I’m doing the naïve thing right now of just having the legacy view call setViewContent
instead of setContent
, which takes a CompositionReference
. However, the leaf composable doesn’t seem to be getting model updates, and throws an `IllegalStateException`: “Unsupported node type LayoutNode” if I try composing something like a Text
.
I’m sure all this infrastructure is full of holes right now, but is this something that’s ideally supposed to work? setViewContent
has a TODO on it to remove, and the only other API that seems relevant here is subcomposeInto
but that requires a ComponentNode
which I haven’t tried to figure out how to get yet.Andrey Kulikov
05/13/2020, 10:45 AMZach Klippenstein (he/him) [MOD]
05/13/2020, 2:02 PMDoes this mean the code here needs to change, or that// TODO: This should probably create a child composition of the original
Recomposer
should be responsible for propagating ambients across compositions, like I'm trying to use CompositionReference
for?compositionReference()
is pretty straightforward and seems to be exactly what I want, so i guess another way to ask that is, is Recomposer
replacing CompositionReference
at some point?Recomposer
and it looks like it's singularly responsible for scheduling and performing composition passes. There's nothing in there about ambients.Leland Richardson [G]
05/13/2020, 6:03 PMsetViewContent
call?
I am not sure how well we handle going back and forth between tree types right now… especially with a view at the root (which is what setViewContent is).
long term setViewContent
is likely to go away, and setContent
will handle both types of trees regardless of what you do.Zach Klippenstein (he/him) [MOD]
05/13/2020, 7:09 PM@Composable Foo() {
Providers(MyAmbient provides foo) {
AndroidView(R.layout.bar) { view ->
view.setContent {
Baz(MyAmbient.current)
}
}
}
}
And I want that innermost composable to be able to access ambients from the outer one.Leland Richardson [G]
05/13/2020, 7:43 PMcompositionRefernce()
but i’m not sure that our newer setContent APIs are making that easysubcomposeInto
specifying a compositionRefernce
and Recomposer.current()
setContent
Zach Klippenstein (he/him) [MOD]
05/13/2020, 8:02 PMLeland Richardson [G]
05/13/2020, 8:29 PMZach Klippenstein (he/him) [MOD]
05/13/2020, 8:34 PMLeland Richardson [G]
05/13/2020, 8:43 PMZach Klippenstein (he/him) [MOD]
05/13/2020, 9:31 PMif you want invalidations to flow through correctly you really need to think of them as one composition.That makes sense, and it matches the behavior I’m seeing initially. But when the value provided by the ambient changes, the child compositions aren’t seeing the value. I suspect this is a bug, will file as well.
ViewGroup.setContent
implementation with the additional CompositionReference
parameter.
It seems there are two keys to using subcomposition that aren't immediately obvious. The first one is that, for the subcomposition to recompose after changing some observed State
, you have to call setContent
explicitly. I tried only calling it once, when setting up the Android View, and it did the initial compose pass successfully, but mutating a State
object observed in the child composition didn't trigger recomposition. I even tried having the child pass its invalidate
function out and calling it directly, which didn't work. I didn't think this would be necessary because the normal setContent
doesn't require constantly being re-invoked. However, I noticed that both WithConstraints
and AdapterList
call subcomposeInto
multiple times so I guess that's expected. They also always call nextFrame
after doing so, but I couldn't see any difference in behavior with or without calling that.
Secondly, and this is where it seems like there's a bug, the above strategy of calling setContent
multiple times works for keeping the content inside the composition fresh. However, on the next composition pass, the subcomposition suddenly loses any custom ambients provided from the parent (they all "reset" back to their default values). If I add a second Android view, sibling to the first one, with its own subcomposition, it also works on the first composition pass and then loses its ambients on the next one.
There's a lot going on here so I apologise if my explanation is confusing. I can pull the code in question into a separate repository, but wanted to get a quick sanity check here first. Am I just too far in the weeds here, and I should stop wasting my time until the API to do this is actually public?LayoutNode
is complaining about not being measured, and in another an NPE because DensityAmbient.current
seems to be returning null, which as far as I can tell shouldn’t even be possible.
I think the issue has something to do with how Android views and LayoutNodes are initialized when they are nested recursively, alternating between the two (i.e. Subcomposition inside View inside Composition inside View). This seems to be more problematic if the “View inside Composition” is initialized inside a composable before passing it to AndroidView
. If I use a custom view and add that to the composition before trying to initialize the child android view, it works.
The only case I haven’t figured out so far is that I get the LayoutNode measure exception when forcing a recomposition of a “root” composition (no parent CompositionReference). It seems like compositions with a parent must be recomposed explicitly, but compositions without a parent must not be composed more than once. I have to handle those cases separately – if there’s a parent, I call setContent
on every update, and if there’s no parent, I call setContent
only once and use a MutableState
to pass updates in. I have no idea why any of this works, or why ambients will sometimes disappear if things are done the wrong way, but at least all this weirdness can be hidden and when it works, it’s pretty magical.Leland Richardson [G]
05/21/2020, 2:22 PMZach Klippenstein (he/him) [MOD]
05/21/2020, 4:37 PMsetContent
multiple times crashes, but it should not. I’ve narrowed this down to probably having something to do with capturing lambdas, filed as https://issuetracker.google.com/issues/157430448.