Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make sure events are not assumed and always update to lifecycle obser… #1230

Closed
wants to merge 3 commits into from

Conversation

ekeitho
Copy link
Contributor

@ekeitho ekeitho commented Nov 11, 2024

This PR addresses an IllegalStateException caused by premature state consumption in WorkflowLifecycleOwner.

This issue was uncovered once we started to host views in a compose container.

java.lang.IllegalStateException: You can consumeRestoredStateForKey only after super.onCreate of corresponding 
component at androidx.savedstate.SavedStateRegistry.consumeRestoredStateForKey(SavedStateRegistry.kt:72) at 
androidx.compose.ui.platform.DisposableSaveableStateRegistry_androidKt.DisposableSaveableStateRegistry(DisposableSaveableStateRegistry.android.kt:75) at 
\androidx.compose.ui.platform.DisposableSaveableStateRegistry_androidKt.DisposableSaveableStateRegistry(DisposableSaveableStateRegistry.android.kt:54) at 
\androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:109) at 
\androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:155) at 
\androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:154) at 
\androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109) at \androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35) at 
\androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:401) at 
\androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:154) at 

Before the Change:

Premature Lifecycle Updates:

The lifecycle might have been updated without considering the exact event, leading to situations where Compose tried to consume state before the component was fully initialized.

Event Misalignment:

Without passing explicit events, the lifecycle transitions could have been misaligned with the actual lifecycle of the Android component.

After the Change:

Explicit Event Handling:

By passing specific Lifecycle.Event instances (ON_START, ON_PAUSE, ON_DESTROY) to updateLifecycle, we synchronize the WorkflowLifecycleOwner's state transitions with the Android component's lifecycle events.

Preventing Premature State Consumption:

Ensuring that lifecycle transitions occur after relevant events (like super.onCreate) prevents Compose from attempting to restore state too early.

Consistent Lifecycle State Management:

The RealWorkflowLifecycleOwner now accurately reflects the View's attachment state, maintaining a coherent lifecycle state that aligns with Android's expectations.

Copy link
Contributor

@rjrjr rjrjr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the right direction except it breaks destroyOnDetach.

Comment on lines 195 to 200
updateLifecycle(Event.ON_START)
}

override fun onViewDetachedFromWindow(v: View) {
viewIsAttachedToWindow = false
updateLifecycle()
updateLifecycle(ON_PAUSE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels really good.

updateLifecycle()
// we should always consume what the new event is from onStateChanged. if we try to
// assume the event like we did in the past, we can get into scenarios where we try to put
// a view in created, when it's parent hasn't completed fully it's super.onCreate yet.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// a view in created, when it's parent hasn't completed fully it's super.onCreate yet.
// a view in created, when its parent hasn't completed fully its super.onCreate yet.

}

override fun destroyOnDetach() {
if (!destroyOnDetach) {
destroyOnDetach = true
updateLifecycle()
updateLifecycle(Event.ON_DESTROY)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This subverts the purpose of destroyOnDetach(), which is to make sure things are still alive while the view is visible. I think instead you want to change onViewDetachedFromWindow to look at destroyOnDetach and use that to choose between ON_PAUSE and ON_DESTROY. Maybe also need to change the guard clause at the top of onViewAttachedToWindow to look directly at destroyOnDetach.

@@ -225,6 +225,9 @@ internal class RealWorkflowLifecycleOwner(
}

localLifecycle.currentState = when {
event != null -> {
event.targetState
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to make sure the local lifecycle isn't greater than the parent? E.g. if the parent hasn't STARTED yet, we shouldn't either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be renamed to parentEvent since the events should originate from the parent lifecycle. For example, onStateChanged ensures that the local lifecycle mirrors the parent's state and doesn't exceed it.

@ekeitho ekeitho force-pushed the kabdulla/workflow-lifecycle branch 2 times, most recently from a95006b to a3d910b Compare November 11, 2024 22:20
@ekeitho ekeitho force-pushed the kabdulla/workflow-lifecycle branch from a3d910b to 93ca790 Compare November 12, 2024 19:27
@ekeitho ekeitho force-pushed the kabdulla/workflow-lifecycle branch from 93ca790 to 324945a Compare November 12, 2024 22:18
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
}

override fun destroyOnDetach() {
if (!destroyOnDetach) {
destroyOnDetach = true
updateLifecycle()
updateLifecycle(Event.ON_DESTROY.targetState)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure the comment doesn't get lost across updates: we can't ship this way.

  • destroyOnDetach() has to mean "move to destroyed the next time onViewDetachedFromWindow() is called.

@rjrjr rjrjr self-requested a review November 12, 2024 23:14
@ekeitho ekeitho closed this Nov 19, 2024
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.

4 participants