React-native: Appearance addChangeListener handler is called when app goes to background with wrong color scheme

Created on 4 Apr 2020  路  10Comments  路  Source: facebook/react-native

Description

A handler function of Appearance.addChangeListener is triggered when the app goes to the background. It also has a wrong colorScheme value.

React Native version:

System:
    OS: macOS 10.15.1
    CPU: (4) x64 Intel(R) Core(TM) i7-5557U CPU @ 3.10GHz
    Memory: 92.24 MB / 16.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 10.13.0 - /usr/local/bin/node
    Yarn: 1.12.1 - /usr/local/bin/yarn
    npm: 6.9.0 - /usr/local/bin/npm
    Watchman: Not Found
  Managers:
    CocoaPods: 1.8.4 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: iOS 13.2, DriverKit 19.0, macOS 10.15, tvOS 13.2, watchOS 6.1
    Android SDK:
      API Levels: 23, 25, 26, 27, 28, 29
      Build Tools: 27.0.3, 28.0.2, 28.0.3, 29.0.2
      System Images: android-23 | Google APIs Intel x86 Atom, android-27 | Google Play Intel x86 Atom, android-28 | Google APIs Intel x86 Atom, android-28 | Google Play Intel x86 Atom, android-29 | Google APIs Intel x86 Atom
      Android NDK: Not Found
  IDEs:
    Android Studio: 3.5 AI-191.8026.42.35.5791312
    Xcode: 11.2.1/11B500 - /usr/bin/xcodebuild
  Languages:
    Python: 2.7.15 - /usr/local/bin/python
  npmPackages:
    @react-native-community/cli: Not Found
    react: 16.11.0 => 16.11.0 
    react-native: 0.62.1 => 0.62.1 
  npmGlobalPackages:
    *react-native*: Not Found

Steps To Reproduce

  1. Register Appearance.addChangeListener at the root of the app (I use it with react-native-navigation).
Appearance.addChangeListener(({ colorScheme }) => {
  console.log(colorScheme);
});
  1. Move the app to the background.

Expected Results

No theme changed.

Attention Repro

Most helpful comment

+1, having the same issue!

All 10 comments

:warning: Missing Reproducible Example
:information_source: It looks like your issue is missing a reproducible example. Please provide a Snack or a repository that demonstrates the issue you are reporting in a minimal, complete, and reproducible manner.

Here is a repo: https://github.com/rosskhanas/react-native-appearance-bug

Once the app goes to the background it logs 2 rows - 2 different values to the console - light, and dark.

+1, having the same issue!

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

Not stale

The problem is on iOS only.
When backgrounding the app, perhaps due to a bug on iOS 13 the user interface style changes to the opposite color scheme and then back to the current color scheme immediately afterwards. The best solution is to debounce the notification calls by 10ms like they did on react-native-appearance.

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0,01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:RCTUserInterfaceStyleDidChangeNotification
                                                        object:self
                                                      userInfo:@{
                                                        RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey: self.traitCollection,
                                                      }];
  });

Definitely still seeing this. Moved to expo/react-native-appearance for now.

With this code in my App.js, I am seeing the extra calls but not the flash back and forth between themes:

  const systemScheme = useColorScheme();
  const themeObject = systemScheme === 'light' ? light : dark;

  const toggleTheme = useCallback((colorScheme) => {
    const statusBarTheme = colorScheme === 'light' ? 'dark' : 'light';
    StatusBar.setBarStyle(`${statusBarTheme}-content`);
  }, []);

  useEffect(() => {
    Appearance.addChangeListener(({ colorScheme }) => {
      toggleTheme(colorScheme);
    });
    return () => {
      Appearance.removeChangeListener();
    };
  }, [toggleTheme]);

Hope that helps someone.

I have the same problem on iOS, and I found expo/react-native-appearance have this issue too, I don't know if it was my mistake.

I fix it by Lodash temporary, I don't find a better way.

import _ from 'lodash';

useEffect(() => {
    const handleColorModeChange = async (preferences: Appearance.AppearancePreferences) => {
      console.log(preferences.colorScheme);
      // do your job ....
    };

    // delay 1 second to handle change
    Appearance.addChangeListener(_.throttle(handleColorModeChange, 1000, {
      leading: false,
      trailing: true
    }));

    return () => {
      Appearance.removeChangeListener(handleColorModeChange);
    };
  }, []);

@Macrow your workaround helped. thank you

here it is as a hook to use in lieu of the rn useColorScheme hook. I also used setTimeout instead of throttle

```typescript jsx
import { Appearance, ColorSchemeName } from 'react-native';
import { useEffect, useRef, useState } from 'react';

export default function useColorScheme(delay = 500): NonNullable {
const [colorScheme, setColorScheme] = useState(Appearance.getColorScheme());

let timeout = useRef(null).current;

useEffect(() => {
Appearance.addChangeListener(onColorSchemeChange);

return () => {
  resetCurrentTimeout();
  Appearance.removeChangeListener(onColorSchemeChange);
};

}, []);

function onColorSchemeChange(preferences: Appearance.AppearancePreferences) {
resetCurrentTimeout();

timeout = setTimeout(() => {
  setColorScheme(preferences.colorScheme);
}, delay);

}

function resetCurrentTimeout() {
if (timeout) {
clearTimeout(timeout);
}
}

return colorScheme as NonNullable;
}
```

Was this page helpful?
0 / 5 - 0 ratings