Updated AppState module to use new emitter system

Summary: AppState now subclasses NativeEventEmitter instead of using global RCTDeviceEventEmitter.

Reviewed By: javache

Differential Revision: D3310488

fbshipit-source-id: f0116599223f4411307385c0dab683659d8d63b6
This commit is contained in:
Nick Lockwood 2016-05-23 09:08:51 -07:00 committed by Facebook Github Bot 3
parent c87b737ca1
commit d9737571c4
20 changed files with 187 additions and 264 deletions

View File

@ -66,9 +66,13 @@ typedef void (^ControlBlock)(RCTRootView*);
- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView - (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView
{ {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[rootView.bridge.eventDispatcher sendAppEventWithName:@"rootViewDidChangeIntrinsicSize" [rootView.bridge.eventDispatcher sendAppEventWithName:@"rootViewDidChangeIntrinsicSize"
body:@{@"width": @(rootView.intrinsicSize.width), body:@{@"width": @(rootView.intrinsicSize.width),
@"height": @(rootView.intrinsicSize.height)}]; @"height": @(rootView.intrinsicSize.height)}];
#pragma clang diagnostic pop
} }
@end @end

View File

@ -103,8 +103,13 @@
[[_bridge expect] enqueueJSCall:_JSMethod [[_bridge expect] enqueueJSCall:_JSMethod
args:[_testEvent arguments]]; args:[_testEvent arguments]];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[_eventDispatcher sendDeviceEventWithName:_eventName body:_body]; [_eventDispatcher sendDeviceEventWithName:_eventName body:_body];
#pragma clang diagnostic pop
[_bridge verify]; [_bridge verify];
} }

View File

@ -41,11 +41,16 @@
RCT_EXPORT_MODULE() RCT_EXPORT_MODULE()
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
- (NSArray<NSString *> *)customDirectEventTypes - (NSArray<NSString *> *)customDirectEventTypes
{ {
return @[@"foo"]; return @[@"foo"];
} }
#pragma clang diagnostic pop
@end @end

View File

@ -11,18 +11,12 @@
*/ */
'use strict'; 'use strict';
var Map = require('Map'); const NativeEventEmitter = require('NativeEventEmitter');
var NativeModules = require('NativeModules'); const NativeModules = require('NativeModules');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); const RCTAppState = NativeModules.AppState;
var RCTAppState = NativeModules.AppState;
var logError = require('logError'); const logError = require('logError');
var invariant = require('fbjs/lib/invariant'); const invariant = require('fbjs/lib/invariant');
var _eventHandlers = {
change: new Map(),
memoryWarning: new Map(),
};
/** /**
* `AppState` can tell you if the app is in the foreground or background, * `AppState` can tell you if the app is in the foreground or background,
@ -36,8 +30,9 @@ var _eventHandlers = {
* - `active` - The app is running in the foreground * - `active` - The app is running in the foreground
* - `background` - The app is running in the background. The user is either * - `background` - The app is running in the background. The user is either
* in another app or on the home screen * in another app or on the home screen
* - `inactive` - This is a transition state that currently never happens for * - `inactive` - This is a state that occurs when transitioning between
* typical React Native apps. * foreground & background, and during periods of inactivity such as
* entering the Multitasking view or in the event of an incoming call
* *
* For more information, see * For more information, see
* [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html) * [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html)
@ -75,13 +70,58 @@ var _eventHandlers = {
* state will happen only momentarily. * state will happen only momentarily.
*/ */
var AppState = { class AppState extends NativeEventEmitter {
/** _eventHandlers: Object;
currentState: ?string;
constructor() {
super(RCTAppState);
this._eventHandlers = {
change: new Map(),
memoryWarning: new Map(),
};
// TODO: getCurrentAppState callback seems to be called at a really late stage
// after app launch. Trying to get currentState when mounting App component
// will likely to have the initial value here.
// Initialize to 'active' instead of null.
this.currentState = 'active';
// TODO: this is a terrible solution - in order to ensure `currentState` prop
// is up to date, we have to register an observer that updates it whenever
// the state changes, even if nobody cares. We should just deprecate the
// `currentState` property and get rid of this.
this.addListener(
'appStateDidChange',
(appStateData) => {
this.currentState = appStateData.app_state;
}
);
// TODO: see above - this request just populates the value of `currentState`
// when the module is first initialized. Would be better to get rid of the prop
// and expose `getCurrentAppState` method directly.
RCTAppState.getCurrentAppState(
(appStateData) => {
this.currentState = appStateData.app_state;
},
logError
);
}
/**
* Add a handler to AppState changes by listening to the `change` event type * Add a handler to AppState changes by listening to the `change` event type
* and providing the handler * and providing the handler
*
* TODO: now that AppState is a subclass of NativeEventEmitter, we could deprecate
* `addEventListener` and `removeEventListener` and just use `addListener` and
* `listener.remove()` directly. That will be a breaking change though, as both
* the method and event names are different (addListener events are currently
* required to be globally unique).
*/ */
addEventListener: function( addEventListener(
type: string, type: string,
handler: Function handler: Function
) { ) {
@ -90,24 +130,24 @@ var AppState = {
'Trying to subscribe to unknown event: "%s"', type 'Trying to subscribe to unknown event: "%s"', type
); );
if (type === 'change') { if (type === 'change') {
_eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener( this._eventHandlers[type].set(handler, this.addListener(
'appStateDidChange', 'appStateDidChange',
(appStateData) => { (appStateData) => {
handler(appStateData.app_state); handler(appStateData.app_state);
} }
)); ));
} else if (type === 'memoryWarning') { } else if (type === 'memoryWarning') {
_eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener( this._eventHandlers[type].set(handler, this.addListener(
'memoryWarning', 'memoryWarning',
handler handler
)); ));
} }
}, }
/** /**
* Remove a handler by passing the `change` event type and the handler * Remove a handler by passing the `change` event type and the handler
*/ */
removeEventListener: function( removeEventListener(
type: string, type: string,
handler: Function handler: Function
) { ) {
@ -115,28 +155,14 @@ var AppState = {
['change', 'memoryWarning'].indexOf(type) !== -1, ['change', 'memoryWarning'].indexOf(type) !== -1,
'Trying to remove listener for unknown event: "%s"', type 'Trying to remove listener for unknown event: "%s"', type
); );
if (!_eventHandlers[type].has(handler)) { if (!this._eventHandlers[type].has(handler)) {
return; return;
} }
_eventHandlers[type].get(handler).remove(); this._eventHandlers[type].get(handler).remove();
_eventHandlers[type].delete(handler); this._eventHandlers[type].delete(handler);
}, }
currentState: ('active' : ?string),
}; };
RCTDeviceEventEmitter.addListener( AppState = new AppState();
'appStateDidChange',
(appStateData) => {
AppState.currentState = appStateData.app_state;
}
);
RCTAppState.getCurrentAppState(
(appStateData) => {
AppState.currentState = appStateData.app_state;
},
logError
);
module.exports = AppState; module.exports = AppState;

View File

@ -11,20 +11,8 @@
*/ */
'use strict'; 'use strict';
var warning = require('fbjs/lib/warning'); const AppState = require('AppState');
class AppStateIOS { console.warn('AppStateIOS is deprecated. Use AppState instead');
static addEventListener(type, handler) { module.exports = AppState;
warning(false, 'Cannot listen to AppStateIOS events on Android.');
}
static removeEventListener(type, handler) {
warning(false, 'Cannot remove AppStateIOS listener on Android.');
}
}
AppStateIOS.currentState = null;
module.exports = AppStateIOS;

View File

@ -1,147 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule AppStateIOS
* @flow
*/
'use strict';
var NativeModules = require('NativeModules');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var RCTAppState = NativeModules.AppState;
var logError = require('logError');
var invariant = require('fbjs/lib/invariant');
var _eventHandlers = {
change: new Map(),
memoryWarning: new Map(),
};
/**
* `AppStateIOS` can tell you if the app is in the foreground or background,
* and notify you when the state changes.
*
* AppStateIOS is frequently used to determine the intent and proper behavior when
* handling push notifications.
*
* ### iOS App States
*
* - `active` - The app is running in the foreground
* - `background` - The app is running in the background. The user is either
* in another app or on the home screen
* - `inactive` - This is a state that occurs when transitioning between
* foreground & background, and during periods of inactivity such as
* entering the Multitasking view or in the event of an incoming call
*
* For more information, see
* [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html)
*
* ### Basic Usage
*
* To see the current state, you can check `AppStateIOS.currentState`, which
* will be kept up-to-date. However, `currentState` will be null at launch
* while `AppStateIOS` retrieves it over the bridge.
*
* ```
* getInitialState: function() {
* return {
* currentAppState: AppStateIOS.currentState,
* };
* },
* componentDidMount: function() {
* AppStateIOS.addEventListener('change', this._handleAppStateChange);
* },
* componentWillUnmount: function() {
* AppStateIOS.removeEventListener('change', this._handleAppStateChange);
* },
* _handleAppStateChange: function(currentAppState) {
* this.setState({ currentAppState, });
* },
* render: function() {
* return (
* <Text>Current state is: {this.state.currentAppState}</Text>
* );
* },
* ```
*
* This example will only ever appear to say "Current state is: active" because
* the app is only visible to the user when in the `active` state, and the null
* state will happen only momentarily.
*/
var AppStateIOS = {
/**
* Add a handler to AppState changes by listening to the `change` event type
* and providing the handler
*/
addEventListener: function(
type: string,
handler: Function
) {
invariant(
['change', 'memoryWarning'].indexOf(type) !== -1,
'Trying to subscribe to unknown event: "%s"', type
);
if (type === 'change') {
_eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener(
'appStateDidChange',
(appStateData) => {
handler(appStateData.app_state);
}
));
} else if (type === 'memoryWarning') {
_eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener(
'memoryWarning',
handler
));
}
},
/**
* Remove a handler by passing the `change` event type and the handler
*/
removeEventListener: function(
type: string,
handler: Function
) {
invariant(
['change', 'memoryWarning'].indexOf(type) !== -1,
'Trying to remove listener for unknown event: "%s"', type
);
if (!_eventHandlers[type].has(handler)) {
return;
}
_eventHandlers[type].get(handler).remove();
_eventHandlers[type].delete(handler);
},
// TODO: getCurrentAppState callback seems to be called at a really late stage
// after app launch. Trying to get currentState when mounting App component
// will likely to have the initial value here.
// Initialize to 'active' instead of null.
currentState: ('active' : ?string),
};
RCTDeviceEventEmitter.addListener(
'appStateDidChange',
(appStateData) => {
AppStateIOS.currentState = appStateData.app_state;
}
);
RCTAppState.getCurrentAppState(
(appStateData) => {
AppStateIOS.currentState = appStateData.app_state;
},
logError
);
module.exports = AppStateIOS;

View File

@ -30,31 +30,35 @@ class RCTDeviceEventEmitter extends EventEmitter {
super(sharedSubscriber); super(sharedSubscriber);
this.sharedSubscriber = sharedSubscriber; this.sharedSubscriber = sharedSubscriber;
} }
_nativeEventModule(eventType: ?string) {
if (eventType) {
if (eventType.lastIndexOf('statusBar', 0) === 0) {
console.warn('`%s` event should be registered via the StatusBarIOS module', eventType);
return require('StatusBarIOS');
}
if (eventType.lastIndexOf('keyboard', 0) === 0) {
console.warn('`%s` event should be registered via the Keyboard module', eventType);
return require('Keyboard');
}
if (eventType === 'appStateDidChange' || eventType === 'memoryWarning') {
console.warn('`%s` event should be registered via the AppState module', eventType);
return require('AppState');
}
}
return null;
}
addListener(eventType: string, listener: Function, context: ?Object): EmitterSubscription { addListener(eventType: string, listener: Function, context: ?Object): EmitterSubscription {
if (eventType.lastIndexOf('statusBar', 0) === 0) { const eventModule = this._nativeEventModule(eventType);
console.warn('`%s` event should be registered via the StatusBarIOS module', eventType); return eventModule ? eventModule.addListener(eventType, listener, context)
return require('StatusBarIOS').addListener(eventType, listener, context); : super.addListener(eventType, listener, context);
}
if (eventType.lastIndexOf('keyboard', 0) === 0) {
console.warn('`%s` event should be registered via the Keyboard module', eventType);
return require('Keyboard').addListener(eventType, listener, context);
}
return super.addListener(eventType, listener, context);
} }
removeAllListeners(eventType: ?string) { removeAllListeners(eventType: ?string) {
if (eventType) { const eventModule = this._nativeEventModule(eventType);
if (eventType.lastIndexOf('statusBar', 0) === 0) { (eventModule && eventType) ? eventModule.removeAllListeners(eventType)
console.warn('statusBar events should be unregistered via the StatusBarIOS module'); : super.removeAllListeners(eventType);
return require('StatusBarIOS').removeAllListeners(eventType);
}
if (eventType.lastIndexOf('keyboard', 0) === 0) {
console.warn('keyboard events should be unregistered via the Keyboard module');
return require('Keyboard').removeAllListeners(eventType);
}
}
super.removeAllListeners(eventType);
} }
removeSubscription(subscription: EmitterSubscription) { removeSubscription(subscription: EmitterSubscription) {

View File

@ -279,8 +279,11 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
// Send event // Send event
if (_observingLocation) { if (_observingLocation) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[_bridge.eventDispatcher sendDeviceEventWithName:@"geolocationDidChange" [_bridge.eventDispatcher sendDeviceEventWithName:@"geolocationDidChange"
body:_lastLocationEvent]; body:_lastLocationEvent];
#pragma clang diagnostic pop
} }
// Fire all queued callbacks // Fire all queued callbacks
@ -321,8 +324,11 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
// Send event // Send event
if (_observingLocation) { if (_observingLocation) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[_bridge.eventDispatcher sendDeviceEventWithName:@"geolocationError" [_bridge.eventDispatcher sendDeviceEventWithName:@"geolocationError"
body:jsError]; body:jsError];
#pragma clang diagnostic pop
} }
// Fire all queued error callbacks // Fire all queued error callbacks

View File

@ -51,8 +51,11 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC
if (![status isEqualToString:self->_status]) { if (![status isEqualToString:self->_status]) {
self->_status = status; self->_status = status;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[self->_bridge.eventDispatcher sendDeviceEventWithName:@"networkStatusDidChange" [self->_bridge.eventDispatcher sendDeviceEventWithName:@"networkStatusDidChange"
body:@{@"network_info": status}]; body:@{@"network_info": status}];
#pragma clang diagnostic pop
} }
} }

View File

@ -346,8 +346,11 @@ RCT_EXPORT_MODULE()
} }
NSArray<id> *responseJSON = @[task.requestID, responseText ?: @""]; NSArray<id> *responseJSON = @[task.requestID, responseText ?: @""];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkData" [_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkData"
body:responseJSON]; body:responseJSON];
#pragma clang diagnostic pop
} }
- (void)sendRequest:(NSURLRequest *)request - (void)sendRequest:(NSURLRequest *)request
@ -361,7 +364,10 @@ RCT_EXPORT_MODULE()
RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) { RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
dispatch_async(_methodQueue, ^{ dispatch_async(_methodQueue, ^{
NSArray *responseJSON = @[task.requestID, @((double)progress), @((double)total)]; NSArray *responseJSON = @[task.requestID, @((double)progress), @((double)total)];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[_bridge.eventDispatcher sendDeviceEventWithName:@"didSendNetworkData" body:responseJSON]; [_bridge.eventDispatcher sendDeviceEventWithName:@"didSendNetworkData" body:responseJSON];
#pragma clang diagnostic pop
}); });
}; };
@ -379,8 +385,11 @@ RCT_EXPORT_MODULE()
} }
id responseURL = response.URL ? response.URL.absoluteString : [NSNull null]; id responseURL = response.URL ? response.URL.absoluteString : [NSNull null];
NSArray<id> *responseJSON = @[task.requestID, @(status), headers, responseURL]; NSArray<id> *responseJSON = @[task.requestID, @(status), headers, responseURL];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse" [_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
body:responseJSON]; body:responseJSON];
#pragma clang diagnostic pop
}); });
}; };
@ -401,8 +410,11 @@ RCT_EXPORT_MODULE()
error.code == kCFURLErrorTimedOut ? @YES : @NO error.code == kCFURLErrorTimedOut ? @YES : @NO
]; ];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[_bridge.eventDispatcher sendDeviceEventWithName:@"didCompleteNetworkResponse" [_bridge.eventDispatcher sendDeviceEventWithName:@"didCompleteNetworkResponse"
body:responseJSON]; body:responseJSON];
#pragma clang diagnostic pop
[_tasksByRequestID removeObjectForKey:task.requestID]; [_tasksByRequestID removeObjectForKey:task.requestID];
}); });

View File

@ -52,7 +52,10 @@ RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback)
RCT_EXPORT_METHOD(sendAppEvent:(NSString *)name body:(nullable id)body) RCT_EXPORT_METHOD(sendAppEvent:(NSString *)name body:(nullable id)body)
{ {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[_bridge.eventDispatcher sendAppEventWithName:name body:body]; [_bridge.eventDispatcher sendAppEventWithName:name body:body];
#pragma clang diagnostic pop
} }
RCT_REMAP_METHOD(shouldResolve, shouldResolve_resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_REMAP_METHOD(shouldResolve, shouldResolve_resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

View File

@ -62,9 +62,12 @@ RCT_EXPORT_MODULE()
return; return;
} }
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[_bridge.eventDispatcher [_bridge.eventDispatcher
sendDeviceEventWithName:@"settingsUpdated" sendDeviceEventWithName:@"settingsUpdated"
body:RCTJSONClean([_defaults dictionaryRepresentation])]; body:RCTJSONClean([_defaults dictionaryRepresentation])];
#pragma clang diagnostic pop
} }
/** /**

View File

@ -28,6 +28,7 @@
@property (nonatomic, assign) NSInteger mostRecentEventCount; @property (nonatomic, assign) NSInteger mostRecentEventCount;
@property (nonatomic, strong) NSNumber *maxLength; @property (nonatomic, strong) NSNumber *maxLength;
@property (nonatomic, copy) RCTDirectEventBlock onChange;
@property (nonatomic, copy) RCTDirectEventBlock onSelectionChange; @property (nonatomic, copy) RCTDirectEventBlock onSelectionChange;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;

View File

@ -472,7 +472,7 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
[self _setPlaceholderVisibility]; [self _setPlaceholderVisibility];
_nativeEventCount++; _nativeEventCount++;
if (!self.reactTag) { if (!self.reactTag || !_onChange) {
return; return;
} }
@ -490,8 +490,7 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
} }
_previousTextLength = textLength; _previousTextLength = textLength;
_previousContentHeight = contentHeight; _previousContentHeight = contentHeight;
_onChange(@{
NSDictionary *event = @{
@"text": self.text, @"text": self.text,
@"contentSize": @{ @"contentSize": @{
@"height": @(contentHeight), @"height": @(contentHeight),
@ -499,8 +498,7 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
}, },
@"target": self.reactTag, @"target": self.reactTag,
@"eventCount": @(_nativeEventCount), @"eventCount": @(_nativeEventCount),
}; });
[_eventDispatcher sendInputEventWithName:@"change" body:event];
} }
- (void)textViewDidEndEditing:(UITextView *)textView - (void)textViewDidEndEditing:(UITextView *)textView

View File

@ -35,7 +35,6 @@ RCT_EXTERN const NSInteger RCTTextUpdateLagWarningThreshold;
RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName); RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName);
@protocol RCTEvent <NSObject> @protocol RCTEvent <NSObject>
@required @required
@property (nonatomic, strong, readonly) NSNumber *viewTag; @property (nonatomic, strong, readonly) NSNumber *viewTag;
@ -60,25 +59,25 @@ RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName);
@interface RCTEventDispatcher : NSObject <RCTBridgeModule> @interface RCTEventDispatcher : NSObject <RCTBridgeModule>
/** /**
* Send an application-specific event that does not relate to a specific * Deprecated, do not use.
* view, e.g. a navigation or data update notification.
*/ */
- (void)sendAppEventWithName:(NSString *)name body:(id)body; - (void)sendAppEventWithName:(NSString *)name body:(id)body
__deprecated_msg("Subclass RCTEventEmitter instead");
/** /**
* Send a device or iOS event that does not relate to a specific view, * Deprecated, do not use.
* e.g.rotation, location, keyboard show/hide, background/awake, etc.
*/ */
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body; - (void)sendDeviceEventWithName:(NSString *)name body:(id)body
__deprecated_msg("Subclass RCTEventEmitter instead");
/** /**
* Send a user input event. The body dictionary must contain a "target" * Deprecated, do not use.
* parameter, representing the React tag of the view sending the event
*/ */
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body; - (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
__deprecated_msg("Use RCTDirectEventBlock or RCTBubblingEventBlock instead");
/** /**
* Send a text input/focus event. * Send a text input/focus event. For internal use only.
*/ */
- (void)sendTextEventWithType:(RCTTextEventType)type - (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag reactTag:(NSNumber *)reactTag

View File

@ -126,7 +126,10 @@ RCT_EXPORT_MODULE()
body[@"key"] = key; body[@"key"] = key;
} }
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[self sendInputEventWithName:events[type] body:body]; [self sendInputEventWithName:events[type] body:body];
#pragma clang diagnostic pop
} }
- (void)sendEvent:(id<RCTEvent>)event - (void)sendEvent:(id<RCTEvent>)event

View File

@ -7,8 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#import "RCTBridgeModule.h" #import "RCTEventEmitter.h"
@interface RCTAppState : NSObject<RCTBridgeModule> @interface RCTAppState : RCTEventEmitter
@end @end

View File

@ -16,6 +16,8 @@
static NSString *RCTCurrentAppBackgroundState() static NSString *RCTCurrentAppBackgroundState()
{ {
RCTAssertMainThread();
static NSDictionary *states; static NSDictionary *states;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
@ -37,26 +39,22 @@ static NSString *RCTCurrentAppBackgroundState()
NSString *_lastKnownState; NSString *_lastKnownState;
} }
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE() RCT_EXPORT_MODULE()
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
#pragma mark - Lifecycle #pragma mark - Lifecycle
- (instancetype)init - (NSArray<NSString *> *)supportedEvents
{ {
if ((self = [super init])) { return @[@"appStateDidChange", @"memoryWarning"];
// Needs to be called on the main thread, as it accesses UIApplication
_lastKnownState = RCTCurrentAppBackgroundState();
}
return self;
} }
- (void)setBridge:(RCTBridge *)bridge - (void)startObserving
{ {
_bridge = bridge;
for (NSString *name in @[UIApplicationDidBecomeActiveNotification, for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidEnterBackgroundNotification,
UIApplicationDidFinishLaunchingNotification, UIApplicationDidFinishLaunchingNotification,
@ -75,19 +73,18 @@ RCT_EXPORT_MODULE()
object:nil]; object:nil];
} }
- (void)handleMemoryWarning - (void)stopObserving
{
[_bridge.eventDispatcher sendDeviceEventWithName:@"memoryWarning"
body:nil];
}
- (void)dealloc
{ {
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self];
} }
#pragma mark - App Notification Methods #pragma mark - App Notification Methods
- (void)handleMemoryWarning
{
[self sendEventWithName:@"memoryWarning" body:nil];
}
- (void)handleAppStateDidChange:(NSNotification *)notification - (void)handleAppStateDidChange:(NSNotification *)notification
{ {
NSString *newState; NSString *newState;
@ -102,8 +99,8 @@ RCT_EXPORT_MODULE()
if (![newState isEqualToString:_lastKnownState]) { if (![newState isEqualToString:_lastKnownState]) {
_lastKnownState = newState; _lastKnownState = newState;
[_bridge.eventDispatcher sendDeviceEventWithName:@"appStateDidChange" [self sendEventWithName:@"appStateDidChange"
body:@{@"app_state": _lastKnownState}]; body:@{@"app_state": _lastKnownState}];
} }
} }
@ -115,7 +112,7 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(getCurrentAppState:(RCTResponseSenderBlock)callback RCT_EXPORT_METHOD(getCurrentAppState:(RCTResponseSenderBlock)callback
error:(__unused RCTResponseSenderBlock)error) error:(__unused RCTResponseSenderBlock)error)
{ {
callback(@[@{@"app_state": _lastKnownState}]); callback(@[@{@"app_state": RCTCurrentAppBackgroundState()}]);
} }
@end @end

View File

@ -9,6 +9,7 @@
#import "RCTEventEmitter.h" #import "RCTEventEmitter.h"
#import "RCTAssert.h" #import "RCTAssert.h"
#import "RCTUtils.h"
#import "RCTLog.h" #import "RCTLog.h"
@implementation RCTEventEmitter @implementation RCTEventEmitter
@ -21,9 +22,16 @@
return @""; return @"";
} }
+ (void)initialize
{
if (self != [RCTEventEmitter class]) {
RCTAssert(RCTClassOverridesInstanceMethod(self, @selector(supportedEvents)),
@"You must override the `supportedEvents` method of %@", self);
}
}
- (NSArray<NSString *> *)supportedEvents - (NSArray<NSString *> *)supportedEvents
{ {
RCTAssert(NO, @"You must override the `supportedEvents` method of %@", [self class]);
return nil; return nil;
} }
@ -32,7 +40,8 @@
RCTAssert(_bridge != nil, @"bridge is not set."); RCTAssert(_bridge != nil, @"bridge is not set.");
if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) { if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) {
RCTLogError(@"`%@` is not a supported event type for %@", eventName, [self class]); RCTLogError(@"`%@` is not a supported event type for %@. Supported events are: `%@`",
eventName, [self class], [[self supportedEvents] componentsJoinedByString:@"`, `"]);
} }
if (_listenerCount > 0) { if (_listenerCount > 0) {
[_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" [_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit"
@ -62,7 +71,8 @@
RCT_EXPORT_METHOD(addListener:(NSString *)eventName) RCT_EXPORT_METHOD(addListener:(NSString *)eventName)
{ {
if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) { if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) {
RCTLogError(@"`%@` is not a supported event type for %@", eventName, [self class]); RCTLogError(@"`%@` is not a supported event type for %@. Supported events are: `%@`",
eventName, [self class], [[self supportedEvents] componentsJoinedByString:@"`, `"]);
} }
if (_listenerCount == 0) { if (_listenerCount == 0) {
[self startObserving]; [self startObserving];

View File

@ -180,7 +180,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
((void (*)(id, SEL, id))objc_msgSend)(target, setter, [RCTConvert BOOL:json] ? ^(NSDictionary *body) { ((void (*)(id, SEL, id))objc_msgSend)(target, setter, [RCTConvert BOOL:json] ? ^(NSDictionary *body) {
body = [NSMutableDictionary dictionaryWithDictionary:body]; body = [NSMutableDictionary dictionaryWithDictionary:body];
((NSMutableDictionary *)body)[@"target"] = weakTarget.reactTag; ((NSMutableDictionary *)body)[@"target"] = weakTarget.reactTag;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[weakManager.bridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(name) body:body]; [weakManager.bridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(name) body:body];
#pragma clang diagnostic pop
} : nil); } : nil);
}; };