Feature to listen on window focus events (#25039)

Summary:
Addressed issue: https://github.com/facebook/react-native/issues/24149

On Android, activity's lifecycle events are not triggered when the user pulls down the Status Bar (opening Notification Drawer). In order to know that, you need to override [onWindowFocusChanged method](https://developer.android.com/reference/android/app/Activity.html#onWindowFocusChanged(boolean)).

## Changelog

[Android] [Added] - Adds a new listener for `onWindowFocusChanged`
[JavaScript] [Added] - New event, `focusChanged`, to listen on focus gain/loss
Pull Request resolved: https://github.com/facebook/react-native/pull/25039

Differential Revision: D15644954

Pulled By: cpojer

fbshipit-source-id: 823acffc4287bec4bf56e9f5ffcac65c01cf13d3
This commit is contained in:
Krzysztof Borowy 2019-06-05 15:59:46 -07:00 коммит произвёл Facebook Github Bot
Родитель bf8d918681
Коммит d45818fe47
7 изменённых файлов: 106 добавлений и 19 удалений

Просмотреть файл

@ -25,6 +25,7 @@ const invariant = require('invariant');
*/
class AppState extends NativeEventEmitter {
_eventHandlers: Object;
_supportedEvents = ['change', 'memoryWarning', 'blur', 'focus'];
currentState: ?string;
isAvailable: boolean;
@ -32,10 +33,10 @@ class AppState extends NativeEventEmitter {
super(NativeAppState);
this.isAvailable = true;
this._eventHandlers = {
change: new Map(),
memoryWarning: new Map(),
};
this._eventHandlers = this._supportedEvents.reduce((handlers, key) => {
handlers[key] = new Map();
return handlers;
}, {});
this.currentState = NativeAppState.getConstants().initialAppState;
@ -75,22 +76,43 @@ class AppState extends NativeEventEmitter {
*/
addEventListener(type: string, handler: Function) {
invariant(
['change', 'memoryWarning'].indexOf(type) !== -1,
this._supportedEvents.indexOf(type) !== -1,
'Trying to subscribe to unknown event: "%s"',
type,
);
if (type === 'change') {
this._eventHandlers[type].set(
handler,
this.addListener('appStateDidChange', appStateData => {
handler(appStateData.app_state);
}),
);
} else if (type === 'memoryWarning') {
this._eventHandlers[type].set(
handler,
this.addListener('memoryWarning', handler),
);
switch (type) {
case 'change': {
this._eventHandlers[type].set(
handler,
this.addListener('appStateDidChange', appStateData => {
handler(appStateData.app_state);
}),
);
break;
}
case 'memoryWarning': {
this._eventHandlers[type].set(
handler,
this.addListener('memoryWarning', handler),
);
break;
}
case 'blur':
case 'focus': {
this._eventHandlers[type].set(
handler,
this.addListener('appStateFocusChange', hasFocus => {
if (type === 'blur' && !hasFocus) {
handler();
}
if (type === 'focus' && hasFocus) {
handler();
}
}),
);
}
}
}
@ -101,7 +123,7 @@ class AppState extends NativeEventEmitter {
*/
removeEventListener(type: string, handler: Function) {
invariant(
['change', 'memoryWarning'].indexOf(type) !== -1,
this._supportedEvents.indexOf(type) !== -1,
'Trying to remove listener for unknown event: "%s"',
type,
);

Просмотреть файл

@ -125,6 +125,12 @@ public abstract class ReactActivity extends AppCompatActivity
mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
mDelegate.onWindowFocusChanged(hasFocus);
}
protected final ReactNativeHost getReactNativeHost() {
return mDelegate.getReactNativeHost();
}

Просмотреть файл

@ -182,6 +182,12 @@ public class ReactActivityDelegate {
return false;
}
public void onWindowFocusChanged(boolean hasFocus) {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onWindowFocusChange(hasFocus);
}
}
@TargetApi(Build.VERSION_CODES.M)
public void requestPermissions(
String[] permissions,

Просмотреть файл

@ -703,6 +703,15 @@ public class ReactInstanceManager {
}
}
@ThreadConfined(UI)
public void onWindowFocusChange(boolean hasFocus) {
UiThreadUtil.assertOnUiThread();
ReactContext currentContext = getCurrentReactContext();
if (currentContext != null) {
currentContext.onWindowFocusChange(hasFocus);
}
}
@ThreadConfined(UI)
public void showDevOptionsDialog() {
UiThreadUtil.assertOnUiThread();

Просмотреть файл

@ -36,6 +36,8 @@ public class ReactContext extends ContextWrapper {
new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<ActivityEventListener> mActivityEventListeners =
new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<WindowFocusChangeListener> mWindowFocusEventListeners =
new CopyOnWriteArraySet<>();
private LifecycleState mLifecycleState = LifecycleState.BEFORE_CREATE;
@ -196,6 +198,14 @@ public class ReactContext extends ContextWrapper {
mActivityEventListeners.remove(listener);
}
public void addWindowFocusChangeListener(WindowFocusChangeListener listener) {
mWindowFocusEventListeners.add(listener);
}
public void removeWindowFocusChangeListener(WindowFocusChangeListener listener) {
mWindowFocusEventListeners.remove(listener);
}
/**
* Should be called by the hosting Fragment in {@link Fragment#onResume}
*/
@ -281,6 +291,17 @@ public class ReactContext extends ContextWrapper {
}
}
public void onWindowFocusChange(boolean hasFocus) {
UiThreadUtil.assertOnUiThread();
for (WindowFocusChangeListener listener : mWindowFocusEventListeners) {
try {
listener.onWindowFocusChange(hasFocus);
} catch (RuntimeException e) {
handleException(e);
}
}
}
public void assertOnUiQueueThread() {
Assertions.assertNotNull(mUiMessageQueueThread).assertIsOnThread();
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
package com.facebook.react.bridge;
/*
* Listener for receiving window focus events.
*/
public interface WindowFocusChangeListener {
void onWindowFocusChange(boolean hasFocus);
}

Просмотреть файл

@ -13,6 +13,7 @@ import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WindowFocusChangeListener;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.module.annotations.ReactModule;
@ -23,7 +24,7 @@ import java.util.Map;
@ReactModule(name = AppStateModule.NAME)
public class AppStateModule extends ReactContextBaseJavaModule
implements LifecycleEventListener {
implements LifecycleEventListener, WindowFocusChangeListener {
protected static final String NAME = "AppState";
@ -37,6 +38,7 @@ public class AppStateModule extends ReactContextBaseJavaModule
public AppStateModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addLifecycleEventListener(this);
reactContext.addWindowFocusChangeListener(this);
mAppState = (reactContext.getLifecycleState() == LifecycleState.RESUMED ?
APP_STATE_ACTIVE : APP_STATE_BACKGROUND);
}
@ -76,6 +78,12 @@ public class AppStateModule extends ReactContextBaseJavaModule
// catalyst instance is going to be immediately dropped, and all JS calls with it.
}
@Override
public void onWindowFocusChange(boolean hasFocus) {
getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
.emit("appStateFocusChange", hasFocus);
}
private WritableMap createAppStateEventMap() {
WritableMap appState = Arguments.createMap();
appState.putString("app_state", mAppState);