diff --git a/packages/react-native/Libraries/Utilities/Appearance.js b/packages/react-native/Libraries/Utilities/Appearance.js index e938742a2c5..f8d31a10cd4 100644 --- a/packages/react-native/Libraries/Utilities/Appearance.js +++ b/packages/react-native/Libraries/Utilities/Appearance.js @@ -19,9 +19,8 @@ import NativeAppearance, { } from './NativeAppearance'; import invariant from 'invariant'; -type AppearanceListener = (preferences: AppearancePreferences) => void; const eventEmitter = new EventEmitter<{ - change: [AppearancePreferences], + change: [{colorScheme: ?ColorSchemeName}], }>(); type NativeAppearanceEventDefinitions = { @@ -32,24 +31,15 @@ if (NativeAppearance != null) { new NativeEventEmitter( NativeAppearance, ).addListener('appearanceChanged', (newAppearance: AppearancePreferences) => { - const {colorScheme} = newAppearance; - invariant( - colorScheme === 'dark' || colorScheme === 'light' || colorScheme == null, - "Unrecognized color scheme. Did you mean 'dark' or 'light'?", - ); + const colorScheme = toColorScheme(newAppearance.colorScheme); eventEmitter.emit('change', {colorScheme}); }); } /** - * Note: Although color scheme is available immediately, it may change at any - * time. Any rendering logic or styles that depend on this should try to call - * this function on every render, rather than caching the value (for example, - * using inline styles rather than setting a value in a `StyleSheet`). - * - * Example: `const colorScheme = Appearance.getColorScheme();` - * - * @returns {?ColorSchemeName} Value for the color scheme preference. + * Returns the current color scheme preference. This value may change, so the + * value should not be cached without either listening to changes or using + * the `useColorScheme` hook. */ export function getColorScheme(): ?ColorSchemeName { if (__DEV__) { @@ -59,37 +49,32 @@ export function getColorScheme(): ?ColorSchemeName { return 'light'; } } - - // TODO: (hramos) T52919652 Use ?ColorSchemeName once codegen supports union - const nativeColorScheme: ?string = - NativeAppearance == null ? null : NativeAppearance.getColorScheme() || null; - invariant( - nativeColorScheme === 'dark' || - nativeColorScheme === 'light' || - nativeColorScheme == null, - "Unrecognized color scheme. Did you mean 'dark' or 'light'?", - ); - return nativeColorScheme; + return toColorScheme(NativeAppearance?.getColorScheme()); } +/** + * Updates the current color scheme to the supplied value. + */ export function setColorScheme(colorScheme: ?ColorSchemeName): void { - const nativeColorScheme = colorScheme == null ? 'unspecified' : colorScheme; - - invariant( - colorScheme === 'dark' || colorScheme === 'light' || colorScheme == null, - "Unrecognized color scheme. Did you mean 'dark', 'light' or null?", - ); - - if (NativeAppearance != null && NativeAppearance.setColorScheme != null) { - NativeAppearance.setColorScheme(nativeColorScheme); - } + NativeAppearance?.setColorScheme(toColorScheme(colorScheme) ?? 'unspecified'); } /** * Add an event handler that is fired when appearance preferences change. */ export function addChangeListener( - listener: AppearanceListener, + listener: ({colorScheme: ?ColorSchemeName}) => void, ): EventSubscription { return eventEmitter.addListener('change', listener); } + +/** + * TODO: (hramos) T52919652 Use ?ColorSchemeName once codegen supports union + */ +function toColorScheme(colorScheme: ?string): ?ColorSchemeName { + invariant( + colorScheme === 'dark' || colorScheme === 'light' || colorScheme == null, + "Unrecognized color scheme. Did you mean 'dark', 'light' or null?", + ); + return colorScheme; +} diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index 92e994b858e..e5f4590fdc5 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -9155,11 +9155,10 @@ declare export default typeof UTFSequence; `; exports[`public API should not change unintentionally Libraries/Utilities/Appearance.js 1`] = ` -"type AppearanceListener = (preferences: AppearancePreferences) => void; -declare export function getColorScheme(): ?ColorSchemeName; +"declare export function getColorScheme(): ?ColorSchemeName; declare export function setColorScheme(colorScheme: ?ColorSchemeName): void; declare export function addChangeListener( - listener: AppearanceListener + listener: ({ colorScheme: ?ColorSchemeName }) => void ): EventSubscription; " `; diff --git a/packages/react-native/src/private/specs/modules/NativeAppearance.js b/packages/react-native/src/private/specs/modules/NativeAppearance.js index 27dedcb476a..c12236e7a53 100644 --- a/packages/react-native/src/private/specs/modules/NativeAppearance.js +++ b/packages/react-native/src/private/specs/modules/NativeAppearance.js @@ -14,19 +14,19 @@ import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboMod export type ColorSchemeName = 'light' | 'dark'; -export type AppearancePreferences = {| +export type AppearancePreferences = { // TODO: (hramos) T52919652 Use ?ColorSchemeName once codegen supports union // types. /* 'light' | 'dark' */ colorScheme?: ?string, -|}; +}; export interface Spec extends TurboModule { // TODO: (hramos) T52919652 Use ?ColorSchemeName once codegen supports union // types. /* 'light' | 'dark' */ +getColorScheme: () => ?string; - +setColorScheme?: (colorScheme: string) => void; + +setColorScheme: (colorScheme: string) => void; // RCTEventEmitter +addListener: (eventName: string) => void; diff --git a/packages/rn-tester/js/examples/Appearance/AppearanceExample.js b/packages/rn-tester/js/examples/Appearance/AppearanceExample.js index 08d117d4836..45b5e2b051f 100644 --- a/packages/rn-tester/js/examples/Appearance/AppearanceExample.js +++ b/packages/rn-tester/js/examples/Appearance/AppearanceExample.js @@ -8,10 +8,7 @@ * @flow */ -import type { - AppearancePreferences, - ColorSchemeName, -} from 'react-native/Libraries/Utilities/NativeAppearance'; +import type {ColorSchemeName} from 'react-native/Libraries/Utilities/NativeAppearance'; import {RNTesterThemeContext, themes} from '../../components/RNTesterTheme'; import * as React from 'react'; @@ -19,20 +16,18 @@ import {useEffect, useState} from 'react'; import {Appearance, Button, Text, View, useColorScheme} from 'react-native'; function ColorSchemeSubscription() { - const [colorScheme, setScheme] = useState( + const [colorScheme, setColorScheme] = useState( Appearance.getColorScheme(), ); useEffect(() => { const subscription = Appearance.addChangeListener( - (preferences: AppearancePreferences) => { - const {colorScheme: scheme} = preferences; - setScheme(scheme); + ({colorScheme: newColorScheme}: {colorScheme: ?ColorSchemeName}) => { + setColorScheme(newColorScheme); }, ); - - return () => subscription?.remove(); - }, [setScheme]); + return () => subscription.remove(); + }, [setColorScheme]); return (