AppState.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow
  9. */
  10. 'use strict';
  11. const EventEmitter = require('../vendor/emitter/EventEmitter');
  12. const NativeEventEmitter = require('../EventEmitter/NativeEventEmitter');
  13. const invariant = require('invariant');
  14. const logError = require('../Utilities/logError');
  15. import NativeAppState from './NativeAppState';
  16. /**
  17. * `AppState` can tell you if the app is in the foreground or background,
  18. * and notify you when the state changes.
  19. *
  20. * See https://reactnative.dev/docs/appstate.html
  21. */
  22. class AppState extends NativeEventEmitter {
  23. _eventHandlers: Object;
  24. _supportedEvents = ['change', 'memoryWarning', 'blur', 'focus'];
  25. currentState: ?string;
  26. isAvailable: boolean;
  27. constructor() {
  28. super(NativeAppState);
  29. this.isAvailable = true;
  30. this._eventHandlers = this._supportedEvents.reduce((handlers, key) => {
  31. handlers[key] = new Map();
  32. return handlers;
  33. }, {});
  34. this.currentState = NativeAppState.getConstants().initialAppState;
  35. let eventUpdated = false;
  36. // TODO: this is a terrible solution - in order to ensure `currentState`
  37. // prop is up to date, we have to register an observer that updates it
  38. // whenever the state changes, even if nobody cares. We should just
  39. // deprecate the `currentState` property and get rid of this.
  40. this.addListener('appStateDidChange', appStateData => {
  41. eventUpdated = true;
  42. this.currentState = appStateData.app_state;
  43. });
  44. // TODO: see above - this request just populates the value of `currentState`
  45. // when the module is first initialized. Would be better to get rid of the
  46. // prop and expose `getCurrentAppState` method directly.
  47. NativeAppState.getCurrentAppState(appStateData => {
  48. // It's possible that the state will have changed here & listeners need to be notified
  49. if (!eventUpdated && this.currentState !== appStateData.app_state) {
  50. this.currentState = appStateData.app_state;
  51. this.emit('appStateDidChange', appStateData);
  52. }
  53. }, logError);
  54. }
  55. // TODO: now that AppState is a subclass of NativeEventEmitter, we could
  56. // deprecate `addEventListener` and `removeEventListener` and just use
  57. // addListener` and `listener.remove()` directly. That will be a breaking
  58. // change though, as both the method and event names are different
  59. // (addListener events are currently required to be globally unique).
  60. /**
  61. * Add a handler to AppState changes by listening to the `change` event type
  62. * and providing the handler.
  63. *
  64. * See https://reactnative.dev/docs/appstate.html#addeventlistener
  65. */
  66. addEventListener(type: string, handler: Function) {
  67. invariant(
  68. this._supportedEvents.indexOf(type) !== -1,
  69. 'Trying to subscribe to unknown event: "%s"',
  70. type,
  71. );
  72. switch (type) {
  73. case 'change': {
  74. this._eventHandlers[type].set(
  75. handler,
  76. this.addListener('appStateDidChange', appStateData => {
  77. handler(appStateData.app_state);
  78. }),
  79. );
  80. break;
  81. }
  82. case 'memoryWarning': {
  83. this._eventHandlers[type].set(
  84. handler,
  85. this.addListener('memoryWarning', handler),
  86. );
  87. break;
  88. }
  89. case 'blur':
  90. case 'focus': {
  91. this._eventHandlers[type].set(
  92. handler,
  93. this.addListener('appStateFocusChange', hasFocus => {
  94. if (type === 'blur' && !hasFocus) {
  95. handler();
  96. }
  97. if (type === 'focus' && hasFocus) {
  98. handler();
  99. }
  100. }),
  101. );
  102. }
  103. }
  104. }
  105. /**
  106. * Remove a handler by passing the `change` event type and the handler.
  107. *
  108. * See https://reactnative.dev/docs/appstate.html#removeeventlistener
  109. */
  110. removeEventListener(type: string, handler: Function) {
  111. invariant(
  112. this._supportedEvents.indexOf(type) !== -1,
  113. 'Trying to remove listener for unknown event: "%s"',
  114. type,
  115. );
  116. if (!this._eventHandlers[type].has(handler)) {
  117. return;
  118. }
  119. this._eventHandlers[type].get(handler).remove();
  120. this._eventHandlers[type].delete(handler);
  121. }
  122. }
  123. function throwMissingNativeModule() {
  124. invariant(
  125. false,
  126. 'Cannot use AppState module when native RCTAppState is not included in the build.\n' +
  127. 'Either include it, or check AppState.isAvailable before calling any methods.',
  128. );
  129. }
  130. class MissingNativeAppStateShim extends EventEmitter {
  131. // AppState
  132. isAvailable: boolean = false;
  133. currentState: ?string = null;
  134. addEventListener(type: string, handler: Function) {
  135. throwMissingNativeModule();
  136. }
  137. removeEventListener(type: string, handler: Function) {
  138. throwMissingNativeModule();
  139. }
  140. // EventEmitter
  141. addListener() {
  142. throwMissingNativeModule();
  143. }
  144. removeAllListeners() {
  145. throwMissingNativeModule();
  146. }
  147. removeSubscription() {
  148. throwMissingNativeModule();
  149. }
  150. }
  151. // This module depends on the native `RCTAppState` module. If you don't include it,
  152. // `AppState.isAvailable` will return `false`, and any method calls will throw.
  153. // We reassign the class variable to keep the autodoc generator happy.
  154. const AppStateInstance: AppState | MissingNativeAppStateShim = NativeAppState
  155. ? new AppState()
  156. : new MissingNativeAppStateShim();
  157. module.exports = AppStateInstance;