Browse Source

Support headlessJs (#1444)

Currently startApp is called from global context which results in the
app being launched when headlessJs tasks run in the background.
In order to support this use case, while not breaking existing users,
this commit adds two mechanisms which should help users detrmine if
the native Activity is running and startApp can be called safely.

1. RNN.appLaunched event is emitted is SplashActivity starts and react
   context has already been created. This is the use case of openeing
   the app while headless js task was started or has just finished and
   react context is in a "pasued" state.

2. Navigation.isAppLaunched() convenience method has been added.
   It returns a promise which when resolved, indicates if the app
   is launched and we should show the ui or not.

Usage

import {Navigation, NativeEventsReceiver} from 'react-native-navigation';

Promise.resolve(Navigation.isAppLaunched())
  .then(appLaunched => {
    if (appLaunched) {
      startApp();
    } else {
      new NativeEventsReceiver().appLaunched(startApp);
    }
  });

function startApp() {
  Navigation.startTabBasedApp({ ... });
}
Guy Carmeli 7 years ago
parent
commit
8acfe41f04

+ 7
- 0
android/app/src/main/java/com/reactnativenavigation/bridge/EventEmitter.java View File

@@ -52,4 +52,11 @@ public class EventEmitter {
52 52
         }
53 53
         reactGateway.getReactEventEmitter().sendEvent(eventId, Arguments.createMap());
54 54
     }
55
+
56
+    public void sendAppLaunchedEvent() {
57
+        if (!NavigationApplication.instance.isReactContextInitialized()) {
58
+            return;
59
+        }
60
+        reactGateway.getReactEventEmitter().sendEvent("RNN.appLaunched", Arguments.createMap());
61
+    }
55 62
 }

+ 5
- 0
android/app/src/main/java/com/reactnativenavigation/bridge/NavigationReactModule.java View File

@@ -258,4 +258,9 @@ public class NavigationReactModule extends ReactContextBaseJavaModule {
258 258
     public void getOrientation(Promise promise) {
259 259
         NavigationCommandsHandler.getOrientation(promise);
260 260
     }
261
+
262
+    @ReactMethod
263
+    public void isAppLaunched(Promise promise) {
264
+        NavigationCommandsHandler.isAppLaunched(promise);
265
+    }
261 266
 }

+ 5
- 0
android/app/src/main/java/com/reactnativenavigation/controllers/NavigationCommandsHandler.java View File

@@ -535,4 +535,9 @@ public class NavigationCommandsHandler {
535 535
         }
536 536
         promise.resolve(OrientationHelper.getOrientation(currentActivity));
537 537
     }
538
+
539
+    public static void isAppLaunched(Promise promise) {
540
+        final boolean isAppLaunched = SplashActivity.isResumed || NavigationActivity.currentActivity != null;
541
+        promise.resolve(isAppLaunched);
542
+    }
538 543
 }

+ 10
- 0
android/app/src/main/java/com/reactnativenavigation/controllers/SplashActivity.java View File

@@ -11,6 +11,7 @@ import com.reactnativenavigation.NavigationApplication;
11 11
 import com.reactnativenavigation.react.ReactDevPermission;
12 12
 
13 13
 public abstract class SplashActivity extends AppCompatActivity {
14
+    public static boolean isResumed = false;
14 15
 
15 16
     @Override
16 17
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -22,8 +23,11 @@ public abstract class SplashActivity extends AppCompatActivity {
22 23
     @Override
23 24
     protected void onResume() {
24 25
         super.onResume();
26
+        isResumed = true;
25 27
 
26 28
         if (NavigationApplication.instance.getReactGateway().hasStartedCreatingContext()) {
29
+            NavigationApplication.instance.getEventEmitter().sendAppLaunchedEvent();
30
+            overridePendingTransition(0, 0);
27 31
             finish();
28 32
             return;
29 33
         }
@@ -42,6 +46,12 @@ public abstract class SplashActivity extends AppCompatActivity {
42 46
         NavigationApplication.instance.startReactContextOnceInBackgroundAndExecuteJS();
43 47
     }
44 48
 
49
+    @Override
50
+    protected void onPause() {
51
+        super.onPause();
52
+        isResumed = false;
53
+    }
54
+
45 55
     private void setSplashLayout() {
46 56
         final int splashLayout = getSplashLayout();
47 57
         if (splashLayout > 0) {

+ 14
- 0
src/NativeEventsReceiver.js View File

@@ -0,0 +1,14 @@
1
+import {
2
+  NativeAppEventEmitter,
3
+  DeviceEventEmitter,
4
+  Platform
5
+} from 'react-native';
6
+export default class NativeEventsReceiver {
7
+  constructor() {
8
+    this.emitter = Platform.OS === 'android' ? DeviceEventEmitter : NativeAppEventEmitter;
9
+  }
10
+
11
+  appLaunched(callback) {
12
+    this.emitter.addListener('RNN.appLaunched', callback);
13
+  }
14
+}

+ 6
- 1
src/Navigation.js View File

@@ -162,6 +162,10 @@ function handleDeepLink(params = {}) {
162 162
   }
163 163
 }
164 164
 
165
+async function isAppLaunched() {
166
+  return await platformSpecific.isAppLaunched();
167
+}
168
+
165 169
 export default {
166 170
   getRegisteredScreen,
167 171
   registerComponent,
@@ -177,5 +181,6 @@ export default {
177 181
   startSingleScreenApp: startSingleScreenApp,
178 182
   setEventHandler: setEventHandler,
179 183
   clearEventHandler: clearEventHandler,
180
-  handleDeepLink: handleDeepLink
184
+  handleDeepLink: handleDeepLink,
185
+  isAppLaunched: isAppLaunched
181 186
 };

+ 3
- 2
src/deprecated/indexDeprecated.android.js View File

@@ -1,8 +1,9 @@
1 1
 import Navigation from './../Navigation';
2 2
 import SharedElementTransition from './../views/sharedElementTransition';
3
+import NativeEventsReceiver from './../NativeEventsReceiver';
3 4
 
4 5
 module.exports = {
5 6
   Navigation,
6
-  SharedElementTransition
7
+  SharedElementTransition,
8
+  NativeEventsReceiver
7 9
 };
8
-

+ 3
- 1
src/deprecated/indexDeprecated.ios.js View File

@@ -1,9 +1,11 @@
1 1
 import Navigation from './../Navigation';
2 2
 import {NavigationToolBarIOS} from './controllers';
3 3
 import SharedElementTransition from '../views/sharedElementTransition';
4
+import NativeEventsReceiver from './../NativeEventsReceiver';
4 5
 
5 6
 module.exports = {
6 7
   Navigation,
7 8
   NavigationToolBarIOS,
8
-  SharedElementTransition
9
+  SharedElementTransition,
10
+  NativeEventsReceiver
9 11
 };

+ 6
- 1
src/deprecated/platformSpecificDeprecated.android.js View File

@@ -675,6 +675,10 @@ function dismissContextualMenu() {
675 675
   newPlatformSpecific.dismissContextualMenu();
676 676
 }
677 677
 
678
+async function isAppLaunched() {
679
+  return await newPlatformSpecific.isAppLaunched();
680
+}
681
+
678 682
 export default {
679 683
   startTabBasedApp,
680 684
   startSingleScreenApp,
@@ -704,5 +708,6 @@ export default {
704 708
   showSnackbar,
705 709
   dismissSnackbar,
706 710
   showContextualMenu,
707
-  dismissContextualMenu
711
+  dismissContextualMenu,
712
+  isAppLaunched
708 713
 };

+ 6
- 1
src/platformSpecific.android.js View File

@@ -177,6 +177,10 @@ function setScreenStyle(screenInstanceId, style) {
177 177
   NativeReactModule.setScreenStyle(screenInstanceId, style);
178 178
 }
179 179
 
180
+async function isAppLaunched() {
181
+  return await NativeReactModule.isAppLaunched();
182
+}
183
+
180 184
 module.exports = {
181 185
   startApp,
182 186
   push,
@@ -210,5 +214,6 @@ module.exports = {
210 214
   dismissSnackbar,
211 215
   showContextualMenu,
212 216
   dismissContextualMenu,
213
-  setScreenStyle
217
+  setScreenStyle,
218
+  isAppLaunched
214 219
 };