Status Update
Comments
je...@google.com <je...@google.com>
lo...@gmail.com <lo...@gmail.com> #2
Looks like it has nothing with the initiViewTreeOwners()
function after all, that function is just setting some tags on the window.decorView
, which should be fully idempotent, and kinda no-op when called more than once.
So the issue is most likely in ComposeView
itself or its underlying mechanisms.
ad...@google.com <ad...@google.com> #3
Root caused this in the repro case to the window-scoped recomposer being cancelled but being referenced later when the new composition is created for the reattached ComposeView.
lo...@gmail.com <lo...@gmail.com> #4
Does that mean that for this to be fixed, a new recomposer would need to be created each time a view is attached, and, if any, that the existing recomposer has been cancelled?
ap...@google.com <ap...@google.com> #5
Branch: androidx-main
commit 83da539603920a4d33f851ac5de473f413684579
Author: Adam Powell <adamp@google.com>
Date: Wed Sep 08 15:27:34 2021
Fix ComposeView CompositionContext caching
Fix a bug where the CompositionContext cached by ComposeView to handle
being placed into a ViewOverlay could be reused even if that
CompositionContext is a Recomposer that shut down.
Test: RecomposerTests, WindowRecomposerTest
Fixes: 197773820
Relnote: "Recomposer.state has been deprecated and replaced by
Recomposer.currentState to change its type to a StateFlow"
Change-Id: Ic2ab34c19176704fe2f6cd081607dfb92d86ea3c
M compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/ComposeViewOverlayTest.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt
M compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/RecomposerTests.kt
M compose/runtime/runtime/api/restricted_current.txt
M compose/runtime/runtime/api/current.txt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt
M compose/runtime/runtime/api/public_plus_experimental_current.txt
lo...@gmail.com <lo...@gmail.com> #6
Thank you for fixing this!
I've been able to remove the workaround code I had, and it works like a charm.
All the best, Louis CAD
Description
Hello,
I found a bug that cost me one day to understand what triggers it, and I want to thank Adam Powell for helping me a bit to figure out what's going on.
TL;DR
In short, if you call
setContentView
with the same view hierarchy a second time after the initial render, the composables in the hierarchy will stop recomposing, which breaks/freezes everything in them.Jetpack Compose release version: 1.1.0-alpha02 (also reproduces with 1.0 and 1.0.1)
Android Studio Build: irrelevant
Kotlin version: irrelevant
Reproducer
I attached a self-contained Kotlin file (
MainActivity.kt
) that reproduces the issue.You can paste it in any Compose project over the existing
MainActivity
and try yourself.However, I put below reproducing steps anyway.
Steps to Reproduce:
ComposeView
that displays aState
that changes over time.setContentView
passing that instance in theonCreate
function of the hostComponentActivity
.lifecycleScope
awaitFrame()
, ordelay(1500)
setContentView
again, with the very same instance of theComposeView
created earlier.Expected outcome
The composable keeps display the updates of the
State
instance.Actual result
Once the
setContentView
is called the second time, the composable stops displaying the updates of theState
instance.The Slack thread from start to finish
If you want to see what was the process from facing the issue, to finding why it happened, you can take a look at this thread on Kotlin's Slack:
What is likely the culprit IMHO
In the Android platform, the
setContentView
method inActivity
is idempotent. Calling it a second time with the same view hierarchy while the Activity is not in the destroyed state will have no effect.However, that contract seems to have been broken by the
ComponentActivity
class from AndroidX, as it's also calling a function namedinitViewTreeOwners()
.The name of that function indicates that it's been designed for initialization, something which by definition, is supposed to happen only once. However, a setter doesn't generally have such constraints, at least, not because it's a setter (as the
setContentView
function is).I think that this
initViewTreeOwners()
function being called again is what messes upComposeView
or its underlying mechanisms, putting them in a state they should not be in.However, the issue might not be in
ComponentActivity
itself, but in the Compose runtime, or both.Thanks for reading, and have a great day!
Louis CAD