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

[iOS] useColorScheme changes value when app is backgrounded #35972

Closed
ealmiladi opened this issue Jan 26, 2023 · 21 comments
Closed

[iOS] useColorScheme changes value when app is backgrounded #35972

ealmiladi opened this issue Jan 26, 2023 · 21 comments
Labels

Comments

@ealmiladi
Copy link

ealmiladi commented Jan 26, 2023

Description

On a fresh npx react-native init {appName} project on iOS, if you console.log({isDarkMode}) and move the app to the background, you'll notice these sets of logs. It seems that the useColorScheme hook is reporting that the user's theme setting has been changed, although it hasn't changed at all. Bringing the app to the foreground again resolves the issue, but causes screen flickers on elements that rely on the value. This issue does not affect Android.

 LOG  {"isDarkMode": true} // app is in foreground and is currently in dark mode
 LOG  {"isDarkMode": false} // app is backgrounded and is _still_ in dark mode
 LOG  {"isDarkMode": true} // app is in foreground

Version

0.70.6 - 0.71.1

Output of npx react-native info

System:
OS: macOS 12.5.1
CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Memory: 45.07 MB / 32.00 GB
Shell: 5.8.1 - /usr/local/bin/zsh
Binaries:
Node: 14.18.1 - ~/.nvm/versions/node/v14.18.1/bin/node
Yarn: 1.22.17 - /usr/local/bin/yarn
npm: 6.14.15 - ~/.nvm/versions/node/v14.18.1/bin/npm
Watchman: Not Found
Managers:
CocoaPods: 1.11.3 - /Users/ealimadi/.rvm/gems/ruby-2.7.5/bin/pod
SDKs:
iOS SDK:
Platforms: DriverKit 22.1, iOS 16.1, macOS 13.0, tvOS 16.1, watchOS 9.1
Android SDK: Not Found
IDEs:
Android Studio: 2021.3 AI-213.7172.25.2113.9123335
Xcode: 14.1/14B47b - /usr/bin/xcodebuild
Languages:
Java: 11.0.13 - /usr/bin/javac
npmPackages:
@react-native-community/cli: Not Found
react: 18.1.0 => 18.1.0
react-native: 0.70.6 => 0.70.6
react-native-macos: Not Found
npmGlobalPackages:
react-native: Not Found

Steps to reproduce

  1. npx react-native init demo
  2. Run iOS
  3. console.log({isDarkMode})
  4. Put app into background
  5. Put app back into foreground
  6. Review generated logs

Snack, code example, screenshot, or link to a repository

function App(): JSX.Element {
  const isDarkMode = useColorScheme() === 'dark';
  console.log({isDarkMode})

  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  };

  return (
    <SafeAreaView style={backgroundStyle}>
      <StatusBar
        barStyle={isDarkMode ? 'light-content' : 'dark-content'}
        backgroundColor={backgroundStyle.backgroundColor}
      />
      <ScrollView
        contentInsetAdjustmentBehavior="automatic"
        style={backgroundStyle}>
        <Header />
        <View
          style={{
            backgroundColor: isDarkMode ? Colors.black : Colors.white,
          }}>
          <Section title="Step One">
            Edit <Text style={styles.highlight}>App.tsx</Text> to change this
            screen and then come back to see your edits.
          </Section>
          <Section title="See Your Changes">
            <ReloadInstructions />
          </Section>
          <Section title="Debug">
            <DebugInstructions />
          </Section>
          <Section title="Learn More">
            Read the docs to discover what to do next:
          </Section>
          <LearnMoreLinks />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
}
@sdmoir
Copy link

sdmoir commented Feb 3, 2023

I'm experiencing this same issue on React Native 0.67.4.

My workaround for the time being to prevent screen flickering:

const colorScheme = useColorScheme()
const [currentColorScheme, setCurrentColorScheme] = useState(colorScheme)
const onColorSchemeChange = useRef<NodeJS.Timeout>()

// Add a 1 second delay before switching color scheme
// Cancel if color scheme immediately switches back
useEffect(() => {
  if (colorScheme !== currentColorScheme) {
    onColorSchemeChange.current = setTimeout(() => setCurrentColorScheme(colorScheme), 1000)
  } else if (onColorSchemeChange.current) {
    clearTimeout(onColorSchemeChange.current)
  }
}, [colorScheme])

// <App theme={currentColorScheme}> ...

@ealmiladi
Copy link
Author

ealmiladi commented Feb 3, 2023

I'm experiencing this same issue on React Native 0.67.4.

My workaround for the time being to prevent screen flickering:

const colorScheme = useColorScheme()
const [currentColorScheme, setCurrentColorScheme] = useState(colorScheme)
const onColorSchemeChange = useRef<NodeJS.Timeout>()

// Add a 1 second delay before switching color scheme
// Cancel if color scheme immediately switches back
useEffect(() => {
  if (colorScheme !== currentColorScheme) {
    onColorSchemeChange.current = setTimeout(() => setCurrentColorScheme(colorScheme), 1000)
  } else if (onColorSchemeChange.current) {
    clearTimeout(onColorSchemeChange.current)
  }
}, [colorScheme])

// <App theme={currentColorScheme}> ...

Here's my workaround avoiding the useColorScheme hook completely:

    const [currentTheme, setTheme] = useState<ColorSchemeName>(
        getColorScheme(),
    );

    useEffect(() => {
        AppState.addEventListener('change', nextAppState => {
            if (nextAppState === 'active') {
                const theme = getColorScheme();
                setTheme(theme);
            }
        });
    }, []);

@luicfrr
Copy link

luicfrr commented May 2, 2023

any preview when this issue will be fixed?
Having same problem on RN version 0.71.7

@batuhansahan
Copy link

batuhansahan commented May 15, 2023

i am experiencing this one on 0.71.8
only in development not in release

edit:after upgrade 0.72.3 it happens of release too.

@byteab
Copy link

byteab commented Jun 23, 2023

this hook might help, didn't test it tbh 😀

function useForegroundColorScheme() {

   const colorScheme = useColorScheme()
   const lastCorrectColorScheme = useRef(null)
   if(!lastCorrectColorScheme.current) {
       lastCorrectColorScheme.current = colorScheme
   }
   return React.useMemo(() => {
     const appState = AppState.currentState
     if(Platform.OS === 'ios' && appState.match(/inactive|background/)) {
        return lasCorrectColorScheme.current
     } else {
        lastCorrectColorScheme.current = appState
        return lastCorrectColorScheme.current
     }
   }, [colorScheme])
}

@visoft
Copy link

visoft commented Jul 3, 2023

useColorScheme changing in the background is causing Rendered fewer hooks than expected. This may be caused by an accidental early return statement. in React Native 0.71.10 when I use it in App.tsx.

@batuhansahan
Copy link

batuhansahan commented Aug 7, 2023

This error happening to me in release build on iOS.

Steps
1-Open Another app, Open your react-native app
2-Switch other app
3-Switch back to your app

  • useColorScheme shows wrong theme for 1 sec then getting back to correct theme.

React native 0.72.3
Without new architecture.

@YaoHuiJi
Copy link

It happens on RN 0.72.0 too

@uhhhsoyan
Copy link

uhhhsoyan commented Aug 26, 2023

Experiencing this on 0.72.4, hermes enabled but without new architecture

@mhaanstra-wearetriple
Copy link

mhaanstra-wearetriple commented Sep 4, 2023

This is a result of the OS taking what is called a 'snapshot' on iOS, to facilitate the preview when you browse your open applications; a snapshot is required for both light & dark mode in case you change modes while on the open application screen, so the OS will report a brief moment in time both light & dark mode where the app should respond to these modes, or risk a flawed snapshot

@MateWW
Copy link
Contributor

MateWW commented Sep 13, 2023

I've done a deeper investigation on that topic and it is the expected behavior of a native IOS application.

Outcomes

As @mhaanstra-wearetriple mentioned it is called a "snapshot" that IOS will take when you go to the background.
IOS is preparing screenshots of your application in both light and dark mode and will use them in app switcher view.
This mechanism is not perfect in my opinion but on the other hand, it seems to be the only option to handle app switcher especially since a lot of users have tons of applications. Trying to rerender all applications that are open at once to change appearance sounds like an overkill.

React native behaviour

I've validated that mechanism and as you may see in the attached video this works correctly for native apps.
However, react-native stays in one initial appearance mode.

Hypothesis

I've noticed that render on the JS side is done properly but the draw phase is not happening.
It seems to be happening right after the foreground as we see a smooth transition while the mode is changed.

Questions:

  • Do you think this feature should be supported by react native?
  • If not can we trigger an appearance update only if a state is different than background?
50-appearence-issue.mp4

ShevO27 pushed a commit to ShevO27/react-native that referenced this issue 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
@salman-ar-sar
Copy link

This is still an issue on 0.72.6, why was it closed?

@MateWW
Copy link
Contributor

MateWW commented Nov 20, 2023

@salman-ar-sar Fix will be available in 0.73 based on changelog

@salman-ar-sar
Copy link

@salman-ar-sar Fix will be available in 0.73 based on changelog

Oh okay. That's good to hear. Do we have an estimated timeline for the 0.73 release?

@timothyerwin
Copy link

yeah, I think I'm seeing something similar. when I bring it back it's redrawing the entire screen first in dark mode, then back to the light mode...

@timothyerwin
Copy link

This is a result of the OS taking what is called a 'snapshot' on iOS, to facilitate the preview when you browse your open applications; a snapshot is required for both light & dark mode in case you change modes while on the open application screen, so the OS will report a brief moment in time both light & dark mode where the app should respond to these modes, or risk a flawed snapshot

"in case"? lol, seems very lazy and inefficient of the iOS developers here...wouldn't they know when the color scheme changes and only trigger a snapshot then?

@Darren120
Copy link

still exist on new version. using expo. RN 0.73

@git-user-1337
Copy link

Why was this closed if it's still not fixed?

@marcos-vinicius-mafei
Copy link

Still not fixed :/

@Georg7
Copy link

Georg7 commented Jan 19, 2024

This is a terrible user experience - and still not fixed

@RhonnieAl
Copy link

RhonnieAl commented Jul 18, 2024

@MateWW Fix will be available in 0.73 based on changelog

RN 0.74 and still not fixed 🥲

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.