Skip to content

Conversation

n8chur
Copy link
Collaborator

@n8chur n8chur commented Jun 28, 2023

Problem

When a WorkflowHostingController is added to a UIViewController hierarchy, we need to ensure that we re-render the Screen using an updated ViewEnvironment, since the environment from the ancestor path above that WorkflowHostingController could contain customizations that are not present in the WorkflowHostingController itself.

Currently, it is expected that consumers would call setNeedsEnvironmentUpdate() when adding a WorkflowHostingController as a child, but this is fragile and hard to enforce/validate, especially in legacy code.

Omitting this setNeedsEnvironmentUpdate() can cause issues where content would be rendered on screen using invalid ViewEnvironment values on the first rendering. On a later environment update, we'd likely see this environment reflect the correct values, but we want to avoid showing content with bad ViewEnvironment information whenever possible. An example of this issue can be seen in this Slack thread from a in issue reported in ios-register.

Solution

While consumers should still try to call setNeedsEnvironmentUpdate() whenever the environment changes (either through an update in value or an update in path), this PR attempts to catch mistakes and legacy usecases by tracking the ancestor tree node path when state updates and layouts occur, and if that path changes, we'll ensure an update occurs on the next layout pass.

Integration

I've smoke tested this change in ios-register and market and no changes are needed upstream to support this fix. Here are the integration PRs (with no real code changes) that show passing CI jobs:

ios-register: https://github.com/squareup/ios-register/pull/88056
market: https://github.com/squareup/market/pull/6547

@n8chur n8chur force-pushed the westin/update-on-layout-new-hierarchy branch 4 times, most recently from 10c5a29 to 0c16447 Compare June 29, 2023 17:24
@n8chur n8chur force-pushed the westin/update-on-layout-new-hierarchy branch from 0c16447 to cf696af Compare June 29, 2023 17:29
@n8chur n8chur changed the title [Prototype] Update WorkflowHostinController on layout if ancestor hierarchy has changed [fix]: update WorkflowHostingController on layout if ancestor hierarchy has changed Jun 29, 2023
@n8chur n8chur marked this pull request as ready for review June 29, 2023 17:38
@n8chur n8chur requested review from a team as code owners June 29, 2023 17:38
Copy link
Contributor

@kyleve kyleve left a comment

Choose a reason for hiding this comment

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

Clever!

applyEnvironmentIfNeeded()

let environmentAncestorPath = environmentAncestorPath
if environmentAncestorPath != lastEnvironmentAncestorPath {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we not do this if the path is empty? Eg, trying to think if a layout subviews can/will happen after being removed from the hierarchy, causing an extra or invalid layout pass.

Copy link
Collaborator Author

@n8chur n8chur Jun 29, 2023

Choose a reason for hiding this comment

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

The WorkflowHostingController could be become the root of a window where there would be no ancestor node and I think we'd still want to perform an update in that case.

@@ -41,6 +41,8 @@ public final class WorkflowHostingController<ScreenType, Output>: WorkflowUIView

private let (lifetime, token) = Lifetime.make()

private var lastEnvironmentAncestorPath: EnvironmentAncestorPath?
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we might have a potential retain cycle here, if this is storing strong references to the entire VC hierarchy above it. Can we weak-box each ancestor on the path, or does that break our ability to reliably compare paths?

Copy link
Contributor

Choose a reason for hiding this comment

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

It's not a reference since it uses ObjectIdentifier(node) right? Eg, just a pointer.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's also my understanding.

Copy link
Collaborator Author

@n8chur n8chur Jun 29, 2023

Choose a reason for hiding this comment

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

I talked through this with @watt and did some experimentation locally and it does look like ObjectIdentifier just keeps a reference to the pointer value, not the object itself.

One interesting note that @watt pointed out from the documentation of ObjectIdentifier:

This unique identifier is only valid for comparisons during the lifetime of the instance.

Andrew was wondering if the pointer could be reused for a new instance and cause false positives on the equitability check.

This seems fairly unlikely to occur in practice here, but we could add a weak box for these objects to better track the lifetime if we think it's worth the effort.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've updated this to use a weak box and === comparisons: fa85e0f

@n8chur n8chur merged commit 7fea79f into main Jun 29, 2023
@n8chur n8chur deleted the westin/update-on-layout-new-hierarchy branch June 29, 2023 22:31
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.

5 participants