From 118e88393e389ff70e30ada10a69b72dd31d869a Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 17 Feb 2017 09:49:35 -0800 Subject: [PATCH] Guard against missing native module Reviewed By: martinbigio Differential Revision: D4579114 fbshipit-source-id: a2e9f634748e1c56eb9867bfb9e3e66e9cdc5e93 --- Libraries/AppState/AppState.js | 48 +++++++++++++++++++++++- Libraries/Core/Devtools/setupDevtools.js | 3 ++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js index c4ad345bc9e056..072a20094613f1 100644 --- a/Libraries/AppState/AppState.js +++ b/Libraries/AppState/AppState.js @@ -11,6 +11,7 @@ */ 'use strict'; +const EventEmitter = require('EventEmitter'); const NativeEventEmitter = require('NativeEventEmitter'); const NativeModules = require('NativeModules'); const RCTAppState = NativeModules.AppState; @@ -25,6 +26,9 @@ const invariant = require('fbjs/lib/invariant'); * AppState is frequently used to determine the intent and proper behavior when * handling push notifications. * + * This module depends on the native RCTAppState module. If you don't include it, + * `AppState.isAvailable` will return `false`, and any method calls will throw. + * * ### App States * * - `active` - The app is running in the foreground @@ -86,6 +90,7 @@ class AppState extends NativeEventEmitter { _eventHandlers: Object; currentState: ?string; + isAvailable: boolean = true; constructor() { super(RCTAppState); @@ -173,6 +178,45 @@ class AppState extends NativeEventEmitter { } } -AppState = new AppState(); +function throwMissingNativeModule() { + invariant( + false, + 'Cannot use AppState module when native RCTAppState is not included in the build.\n' + + 'Either include it, or check AppState.isAvailable before calling any methods.' + ); +} + +class MissingNativeAppStateShim extends EventEmitter { + // AppState + isAvailable: boolean = false; + currentState: ?string = null; + + addEventListener() { + throwMissingNativeModule(); + } + + removeEventListener() { + throwMissingNativeModule(); + } + + // EventEmitter + addListener() { + throwMissingNativeModule(); + } + + removeAllListeners() { + throwMissingNativeModule(); + } + + removeSubscription() { + throwMissingNativeModule(); + } +} + +// Guard against missing native module by throwing on first method call. +// Keep the API the same so Flow doesn't complain. +const appState = RCTAppState + ? new AppState() + : new MissingNativeAppStateShim(); -module.exports = AppState; +module.exports = appState; diff --git a/Libraries/Core/Devtools/setupDevtools.js b/Libraries/Core/Devtools/setupDevtools.js index 57777c7c77240a..48c2881fa57ef9 100644 --- a/Libraries/Core/Devtools/setupDevtools.js +++ b/Libraries/Core/Devtools/setupDevtools.js @@ -20,6 +20,9 @@ if (__DEV__) { connectToDevTools({ isAppActive() { // Don't steal the DevTools from currently active app. + // Note: if you add any AppState subscriptions to this file, + // you will also need to guard against `AppState.isAvailable`, + // or the code will throw for bundles that don't have it. return AppState.currentState !== 'background'; }, // Special case: Genymotion is running on a different host.