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

fix: over reporting trait changes #39439

Closed

Conversation

alanjhughes
Copy link
Collaborator

@alanjhughes alanjhughes commented Sep 14, 2023

Summary:

Closes #35972
Closes #36713

This PR addresses a couple of issues with useColorScheme and the Appearance API.

  • fix: return the correct default trait collection #38214 introduced a regression. Using to RCTExecuteOnMainQueue was a mistake as we need this to happen synchronously to return the result. Doing it async causes the traitCollection to remain uninitialized.
  • The useColorScheme hook is updating when the app is in the background on iOS and the OS is taking the snapshots for the app switcher. This causes a flash when returning to the app as the correct color is set again. Here, we can check for the app state in traitCollectionDidChange and not send these events when in the background.
  • Removed a line that was left over after some OS version checks were removed when support for iOS 12 was dropped.

Changelog:

[IOS] [FIXED] - Don't send the RCTUserInterfaceStyleDidChangeNotification when the app is in the background.

Test Plan:

Tested on rn-tester, logged the changes whenever useColorScheme updates. It no longer happens when the app is in the background. The returned interface style on the initial render is always correct now.

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Expo Partner: Expo Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Sep 14, 2023
@alanjhughes alanjhughes force-pushed the fix/overreporting-trait-changes branch from cffdb07 to 5715b14 Compare September 14, 2023 08:43
return RCTSharedApplication().delegate.window.traitCollection;
} else {
__block UITraitCollection* traitCollection = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
Copy link
Member

Choose a reason for hiding this comment

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

We should avoid using dispatch_sync as much as possible, as it can lead to deadlocks.

This module is marked as requiresMainQueueSetup, which means the constructor is called from the main thread. Is there any way we can access this attribute there?

Also, as applies to most classes in UIKit, this class is not safe to use outside the main thread. We should grab just the userInterfaceStyle prop, and not leak anything else.

Copy link
Collaborator Author

@alanjhughes alanjhughes Sep 14, 2023

Choose a reason for hiding this comment

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

Something like this?

@implementation RCTAppearance {
  NSString *_currentColorScheme;
}

- (instancetype)init
{
  if ((self = [super init])) {
    UITraitCollection *traitCollection = RCTSharedApplication().delegate.window.traitCollection;
    _currentColorScheme = RCTColorSchemePreference(traitCollection);
  }
  return self;
}

Comment on lines 72 to 80
if ((self = [super init])) {
UITraitCollection *traitCollection = RCTSharedApplication().delegate.window.traitCollection;
_currentColorScheme = RCTColorSchemePreference(traitCollection);
}
return self;
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we should set-up the observer here by default (now in startObserving). Since otherwise _currentColorScheme will go out of date if there's no listeners on the JS side.

@alanjhughes alanjhughes force-pushed the fix/overreporting-trait-changes branch from 18ce003 to 68a4887 Compare September 20, 2023 08:46
@javache javache requested a review from philIip September 20, 2023 09:53
@javache
Copy link
Member

javache commented Sep 20, 2023

7888338 was just backed out, could you rebase this on top of that?

@alanjhughes
Copy link
Collaborator Author

@javache - Yes, that's done, just waiting on CI to finish

Comment on lines +75 to +79
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appearanceChanged:)
name:RCTUserInterfaceStyleDidChangeNotification
object:nil];
}
Copy link
Member

Choose a reason for hiding this comment

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

You should move the logic from stopObserving to invalidate since we always want to keep the listener.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

@facebook-github-bot
Copy link
Contributor

@javache has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@github-actions
Copy link

Warnings
⚠️ One hour and a half have passed and the E2E jobs haven't finished yet.

Generated by 🚫 dangerJS against de742dc

@@ -368,6 +368,9 @@ - (void)contentViewInvalidated
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
if (RCTSharedApplication().applicationState == UIApplicationStateBackground) {
return;
}

Copy link
Member

Choose a reason for hiding this comment

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

To confirm: where do we update the trait collection when the application foregrounds? If you change to dark mode while the app is backgrounded, do we pick it up on foreground?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We'll get it on the foreground but we're in the foreground state before the user sees it so the change isn't visible to them

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 main issue I was trying to solve is when _currentColorScheme was set to nil initially, the default color scheme was light so on first render it would be light regardless of the users settings. So if the users device was in dark mode on initial render it would be reported as light until you toggled it and then it synced up

Copy link
Member

Choose a reason for hiding this comment

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

Right, the changes to _currentColorScheme makes sense, I'm just a bit confused about how the RCTRootView changes are related here. Is it required as part of this PR?

We'll get it on the foreground but we're in the foreground state

Where does this happen? Does traitCollectionDidChange when the application is foregrounded?

Copy link
Collaborator Author

@alanjhughes alanjhughes Sep 25, 2023

Choose a reason for hiding this comment

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

Yes, currently traitCollectionDidChangewill fire twice when the app moves to the background because the OS is taking the snapshots for the app switcher. It'll take one using the current scheme. Switch to the opposite and then switch back to the original. The changes in RCTRootView is just to ignore those changes.

@github-actions
Copy link

This pull request was successfully merged by @alanjhughes in 6118aff.

When will my fix make it into a release? | Upcoming Releases

@github-actions github-actions bot added the Merged This PR has been merged. label Sep 26, 2023
ShevO27 pushed a commit to ShevO27/react-native that referenced this pull request Sep 26, 2023
Summary:
Closes facebook#35972
Closes facebook#36713

This PR addresses a couple of issues with `useColorScheme` and the `Appearance` API.

- facebook#38214 introduced a regression. Using to `RCTExecuteOnMainQueue` was a mistake as we need this to happen synchronously to return the result. Doing it async causes the `traitCollection` to remain uninitialized.
- The `useColorScheme` hook is updating when the app is in the background on iOS and the OS is taking the snapshots for the app switcher. This causes a flash when returning to the app as the correct color is set again. Here, we can check for the app state in `traitCollectionDidChange` and not send these events when in the background.
- Removed a line that was left over after some OS version checks were removed when support for iOS 12 was dropped.

## Changelog:

[IOS] [FIXED] - Don't send the `RCTUserInterfaceStyleDidChangeNotification` when the app is in the background.

Pull Request resolved: facebook#39439

Test Plan: Tested on `rn-tester`, logged the changes whenever `useColorScheme` updates. It no longer happens when the app is in the background. The returned interface style on the initial render is always correct now.

Reviewed By: NickGerleman

Differential Revision: D49454281

Pulled By: javache

fbshipit-source-id: 87e24158a49c50608c79e73fb484442f5aad36a6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. p: Expo Partner: Expo Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
3 participants