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

Allow lifecycle callbacks to safety trigger re-renders #425

Merged
merged 6 commits into from
Jul 18, 2023

Conversation

kyleve
Copy link
Collaborator

@kyleve kyleve commented Feb 8, 2023

This fixes an issue where folks would in some cases have to Dispatch.async the work they perform in their onAppear (or `onDisappear) blocks, because the lifecycle callbacks were triggered inside the update. They're not really parts of the render semantically (they more mean "the layout is done, now the element is visible), so this seems more correct.

To properly test this, I had to move some stuff around:

  1. I copied our test host helper over from Market/Listable/Workflow.
  2. I moved LifecycleObserver up into BlueprintUI, since it's not really a "control".
  3. I SPI'd PassthroughView and moved it into BlueprintUI as well.

@kyleve kyleve requested a review from a team as a code owner February 8, 2023 18:24

var temporaryWindow: UIWindow? = nil

func rootViewController() -> UIViewController {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The two cases in here ensure we can run the tests in both cocoapods world, which provides an app host, and SPM, which does not (there is no option in SPM to provide an app host).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, are you sure this is necessary? The existing lifecycle tests work fine without an app host.

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'm trying to make it work/exist as close to as possible to a "real" UI – hence pulling in the app host changes. While it passed before, there's enough special UIKit behavior that exists when being in a real app that I'd rather we just mirror real apps as much as possible.

Comment on lines 378 to 379
/// This is inc ase the `onAppear` callback triggers a re-render,
/// we don't hit our recurisve update precondition.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I dunno, this makes me nervous. If we allow this then you can get into weird scenarios where there's a render pass with callbacks, and it forces another layout which also has callbacks, and now they're out of order — the recursive render gets to complete its callbacks before the first one. In the worst case you could kick off a loop that never allows the render to complete.

Fundamentally I'm not sure there's a strong justification for anyone needing to force an explicit re-render during onAppear. Why did the element need to change as a result of appearing, instead of just using that element to begin with? Why did it need to change immediately and synchronously? Seems like there are some holes in this story.

Copy link
Member

@robmaceachern robmaceachern left a comment

Choose a reason for hiding this comment

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

I think this change is good. I'm not sure if the debug warning we were talking about has a ton of value, so I can live without it.

The nested callback case pointed out by Andrew is interesting but I'm not 100% sure if we need to address that.

BlueprintUI/Sources/BlueprintView/BlueprintView.swift Outdated Show resolved Hide resolved
@kyleve
Copy link
Collaborator Author

kyleve commented Feb 9, 2023

Once again, annoyed about the extension thing

/Users/runner/work/Blueprint/Blueprint/BlueprintUI/Tests/XCTestCase+AppHost.swift:25:43: error: 'shared' is unavailable in application extensions for iOS: Use view controller based solutions where appropriate instead.
            if let rootVC = UIApplication.shared.delegate?.window??.rootViewController {
                                          ^~~~~~
UIKit.UIApplication:4:20: note: 'shared' has been explicitly marked unavailable here
    open class var shared: UIApplication { get }

Copy link
Collaborator

@watt watt left a comment

Choose a reason for hiding this comment

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

just discovered I had this pending review from a while ago


var temporaryWindow: UIWindow? = nil

func rootViewController() -> UIViewController {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, are you sure this is necessary? The existing lifecycle tests work fine without an app host.

@@ -1,16 +1,16 @@
import UIKit

/// View which does not personally receive touches but allows its subviews to receive touches.
final class PassthroughView: UIView {
@_spi(BlueprintPassthroughView) public final class PassthroughView: UIView {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you pull out these unrelated changes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not unrelated; moving LifecycleObserver, which depends on PassthroughView, means we need to expose this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

(Also sorry, to be clear, one of the reasons I moved this was to get this running under our cocoapods tests – which right now does not run any of the CommonControls tests...)

@robmaceachern robmaceachern merged commit f04c0b7 into main Jul 18, 2023
@robmaceachern robmaceachern deleted the kve/lifecycle-callbacks-outside-update branch July 18, 2023 19:47
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