Skip to content

Commit 6f22989

Browse files
yungstersfacebook-github-bot
authored andcommitted
RN: Simplify AppState
Summary: Simplifies `AppState` by removing redundant methods and changing `addEventListener` to return an `EventSubscription`. Changelog: [General][Changed] - `AppState.addEventListener` now returns an `EventSubscription` object. [General][Removed] - Removed `AppState.removeEventListener`. Instead, use the `remove()` method on the object returned by `AppState.addEventListener`. [General][Removed] - `AppState` no longer inherits from `NativeEventEmitter`, so it no longer implements `addListener`, `removeAllListeners`, and `removeSubscription`. Reviewed By: wtfil Differential Revision: D26161343 fbshipit-source-id: b3cff76bf0f8f7d79cd954fdef551d0654c682ca
1 parent 6774358 commit 6f22989

File tree

2 files changed

+77
-178
lines changed

2 files changed

+77
-178
lines changed

Libraries/AppState/AppState.js

+62-169
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7+
* @flow strict-local
78
* @format
8-
* @flow
99
*/
1010

11+
import {type EventSubscription} from '../vendor/emitter/EventEmitter';
1112
import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
12-
import type EventSubscription from '../vendor/emitter/_EventSubscription';
13-
import type EmitterSubscription from '../vendor/emitter/_EmitterSubscription';
1413
import logError from '../Utilities/logError';
15-
import EventEmitter from '../vendor/emitter/EventEmitter';
1614
import NativeAppState from './NativeAppState';
17-
import invariant from 'invariant';
1815

1916
export type AppStateValues = 'inactive' | 'background' | 'active';
2017

@@ -37,56 +34,48 @@ type NativeAppStateEventDefinitions = {
3734
*
3835
* See https://reactnative.dev/docs/appstate.html
3936
*/
40-
class AppState extends NativeEventEmitter<NativeAppStateEventDefinitions> {
41-
_eventHandlers: {
42-
[key: $Keys<AppStateEventDefinitions>]: Map<
43-
/* Handler */ $FlowFixMe,
44-
EventSubscription<NativeAppStateEventDefinitions, $FlowFixMe>,
45-
>,
46-
...,
47-
};
48-
_supportedEvents: $ReadOnlyArray<$Keys<AppStateEventDefinitions>> = [
49-
'change',
50-
'memoryWarning',
51-
'blur',
52-
'focus',
53-
];
54-
currentState: ?string;
37+
class AppState {
38+
currentState: ?string = null;
5539
isAvailable: boolean;
5640

57-
constructor() {
58-
super(NativeAppState);
59-
60-
this.isAvailable = true;
61-
this._eventHandlers = this._supportedEvents.reduce((handlers, key) => {
62-
handlers[key] = new Map();
63-
return handlers;
64-
}, {});
65-
66-
this.currentState = NativeAppState.getConstants().initialAppState;
67-
68-
let eventUpdated = false;
41+
_emitter: ?NativeEventEmitter<NativeAppStateEventDefinitions>;
6942

70-
// TODO: this is a terrible solution - in order to ensure `currentState`
71-
// prop is up to date, we have to register an observer that updates it
72-
// whenever the state changes, even if nobody cares. We should just
73-
// deprecate the `currentState` property and get rid of this.
74-
this.addListener('appStateDidChange', appStateData => {
75-
eventUpdated = true;
76-
this.currentState = appStateData.app_state;
77-
});
78-
79-
// TODO: see above - this request just populates the value of `currentState`
80-
// when the module is first initialized. Would be better to get rid of the
81-
// prop and expose `getCurrentAppState` method directly.
82-
NativeAppState.getCurrentAppState(appStateData => {
83-
// It's possible that the state will have changed here & listeners need to be notified
84-
if (!eventUpdated && this.currentState !== appStateData.app_state) {
43+
constructor() {
44+
if (NativeAppState == null) {
45+
this.isAvailable = false;
46+
} else {
47+
this.isAvailable = true;
48+
49+
const emitter: NativeEventEmitter<NativeAppStateEventDefinitions> = new NativeEventEmitter(
50+
NativeAppState,
51+
);
52+
this._emitter = emitter;
53+
54+
this.currentState = NativeAppState.getConstants().initialAppState;
55+
56+
let eventUpdated = false;
57+
58+
// TODO: this is a terrible solution - in order to ensure `currentState`
59+
// prop is up to date, we have to register an observer that updates it
60+
// whenever the state changes, even if nobody cares. We should just
61+
// deprecate the `currentState` property and get rid of this.
62+
emitter.addListener('appStateDidChange', appStateData => {
63+
eventUpdated = true;
8564
this.currentState = appStateData.app_state;
86-
// $FlowExpectedError[incompatible-call]
87-
this.emit('appStateDidChange', appStateData);
88-
}
89-
}, logError);
65+
});
66+
67+
// TODO: see above - this request just populates the value of `currentState`
68+
// when the module is first initialized. Would be better to get rid of the
69+
// prop and expose `getCurrentAppState` method directly.
70+
// $FlowExpectedError[incompatible-call]
71+
NativeAppState.getCurrentAppState(appStateData => {
72+
// It's possible that the state will have changed here & listeners need to be notified
73+
if (!eventUpdated && this.currentState !== appStateData.app_state) {
74+
this.currentState = appStateData.app_state;
75+
emitter.emit('appStateDidChange', appStateData);
76+
}
77+
}, logError);
78+
}
9079
}
9180

9281
// TODO: now that AppState is a subclass of NativeEventEmitter, we could
@@ -103,133 +92,37 @@ class AppState extends NativeEventEmitter<NativeAppStateEventDefinitions> {
10392
addEventListener<K: $Keys<AppStateEventDefinitions>>(
10493
type: K,
10594
handler: (...$ElementType<AppStateEventDefinitions, K>) => void,
106-
): void {
107-
invariant(
108-
this._supportedEvents.indexOf(type) !== -1,
109-
'Trying to subscribe to unknown event: "%s"',
110-
type,
111-
);
112-
95+
): EventSubscription {
96+
const emitter = this._emitter;
97+
if (emitter == null) {
98+
throw new Error('Cannot use AppState when `isAvailable` is false.');
99+
}
113100
switch (type) {
114-
case 'change': {
101+
case 'change':
115102
// $FlowIssue[invalid-tuple-arity] Flow cannot refine handler based on the event type
116103
const changeHandler: AppStateValues => void = handler;
117-
this._eventHandlers[type].set(
118-
handler,
119-
this.addListener('appStateDidChange', appStateData => {
120-
changeHandler(appStateData.app_state);
121-
}),
122-
);
123-
break;
124-
}
125-
case 'memoryWarning': {
104+
return emitter.addListener('appStateDidChange', appStateData => {
105+
changeHandler(appStateData.app_state);
106+
});
107+
case 'memoryWarning':
126108
// $FlowIssue[invalid-tuple-arity] Flow cannot refine handler based on the event type
127109
const memoryWarningHandler: () => void = handler;
128-
this._eventHandlers[type].set(
129-
handler,
130-
this.addListener('memoryWarning', memoryWarningHandler),
131-
);
132-
break;
133-
}
134-
110+
return emitter.addListener('memoryWarning', memoryWarningHandler);
135111
case 'blur':
136-
case 'focus': {
112+
case 'focus':
137113
// $FlowIssue[invalid-tuple-arity] Flow cannot refine handler based on the event type
138114
const focusOrBlurHandler: () => void = handler;
139-
this._eventHandlers[type].set(
140-
handler,
141-
this.addListener('appStateFocusChange', hasFocus => {
142-
if (type === 'blur' && !hasFocus) {
143-
focusOrBlurHandler();
144-
}
145-
if (type === 'focus' && hasFocus) {
146-
focusOrBlurHandler();
147-
}
148-
}),
149-
);
150-
}
151-
}
152-
}
153-
154-
/**
155-
* Remove a handler by passing the `change` event type and the handler.
156-
*
157-
* See https://reactnative.dev/docs/appstate.html#removeeventlistener
158-
*/
159-
removeEventListener<K: $Keys<AppStateEventDefinitions>>(
160-
type: K,
161-
handler: (...$ElementType<AppStateEventDefinitions, K>) => void,
162-
) {
163-
invariant(
164-
this._supportedEvents.indexOf(type) !== -1,
165-
'Trying to remove listener for unknown event: "%s"',
166-
type,
167-
);
168-
const subscription = this._eventHandlers[type].get(handler);
169-
if (!subscription) {
170-
return;
115+
return emitter.addListener('appStateFocusChange', hasFocus => {
116+
if (type === 'blur' && !hasFocus) {
117+
focusOrBlurHandler();
118+
}
119+
if (type === 'focus' && hasFocus) {
120+
focusOrBlurHandler();
121+
}
122+
});
171123
}
172-
subscription.remove();
173-
this._eventHandlers[type].delete(handler);
174-
}
175-
}
176-
177-
class MissingNativeModuleError extends Error {
178-
constructor() {
179-
super(
180-
'Cannot use AppState module when native RCTAppState is not included in the build.\n' +
181-
'Either include it, or check AppState.isAvailable before calling any methods.',
182-
);
183-
}
184-
}
185-
186-
class MissingNativeAppStateShim extends EventEmitter<NativeAppStateEventDefinitions> {
187-
// AppState
188-
isAvailable: boolean = false;
189-
currentState: ?string = null;
190-
191-
addEventListener<K: $Keys<AppStateEventDefinitions>>(
192-
type: K,
193-
handler: (...$ElementType<AppStateEventDefinitions, K>) => mixed,
194-
): void {
195-
throw new MissingNativeModuleError();
196-
}
197-
198-
removeEventListener<K: $Keys<AppStateEventDefinitions>>(
199-
type: K,
200-
handler: (...$ElementType<AppStateEventDefinitions, K>) => mixed,
201-
) {
202-
throw new MissingNativeModuleError();
203-
}
204-
205-
// $FlowIssue[invalid-tuple-arity]
206-
addListener<K: $Keys<NativeAppStateEventDefinitions>>(
207-
eventType: K,
208-
// $FlowIssue[incompatible-extend]
209-
listener: (...$ElementType<NativeAppStateEventDefinitions, K>) => mixed,
210-
context: $FlowFixMe,
211-
): EmitterSubscription<NativeAppStateEventDefinitions, K> {
212-
throw new MissingNativeModuleError();
213-
}
214-
215-
removeAllListeners<K: $Keys<NativeAppStateEventDefinitions>>(
216-
eventType: ?K,
217-
): void {
218-
throw new MissingNativeModuleError();
219-
}
220-
221-
removeSubscription<K: $Keys<NativeAppStateEventDefinitions>>(
222-
subscription: EmitterSubscription<NativeAppStateEventDefinitions, K>,
223-
): void {
224-
throw new MissingNativeModuleError();
124+
throw new Error('Trying to subscribe to unknown event: ' + type);
225125
}
226126
}
227127

228-
// This module depends on the native `RCTAppState` module. If you don't include it,
229-
// `AppState.isAvailable` will return `false`, and any method calls will throw.
230-
// We reassign the class variable to keep the autodoc generator happy.
231-
const AppStateInstance: AppState | MissingNativeAppStateShim = NativeAppState
232-
? new AppState()
233-
: new MissingNativeAppStateShim();
234-
235-
module.exports = AppStateInstance;
128+
module.exports = (new AppState(): AppState);

packages/rn-tester/js/examples/AppState/AppStateExample.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
const React = require('react');
1414

15+
import {type EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
1516
const {AppState, Text, View, Platform} = require('react-native');
1617

1718
class AppStateSubscription extends React.Component<
@@ -25,21 +26,26 @@ class AppStateSubscription extends React.Component<
2526
eventsDetected: [],
2627
};
2728

29+
_subscriptions: ?Array<EventSubscription>;
30+
2831
componentDidMount() {
29-
AppState.addEventListener('change', this._handleAppStateChange);
30-
AppState.addEventListener('memoryWarning', this._handleMemoryWarning);
32+
this._subscriptions = [
33+
AppState.addEventListener('change', this._handleAppStateChange),
34+
AppState.addEventListener('memoryWarning', this._handleMemoryWarning),
35+
];
3136
if (Platform.OS === 'android') {
32-
AppState.addEventListener('focus', this._handleFocus);
33-
AppState.addEventListener('blur', this._handleBlur);
37+
this._subscriptions.push(
38+
AppState.addEventListener('focus', this._handleFocus),
39+
AppState.addEventListener('blur', this._handleBlur),
40+
);
3441
}
3542
}
3643

3744
componentWillUnmount() {
38-
AppState.removeEventListener('change', this._handleAppStateChange);
39-
AppState.removeEventListener('memoryWarning', this._handleMemoryWarning);
40-
if (Platform.OS === 'android') {
41-
AppState.removeEventListener('focus', this._handleFocus);
42-
AppState.removeEventListener('blur', this._handleBlur);
45+
if (this._subscriptions != null) {
46+
for (const subscription of this._subscriptions) {
47+
subscription.remove();
48+
}
4349
}
4450
}
4551

0 commit comments

Comments
 (0)