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
         }
52
         }
53
         reactGateway.getReactEventEmitter().sendEvent(eventId, Arguments.createMap());
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
     public void getOrientation(Promise promise) {
258
     public void getOrientation(Promise promise) {
259
         NavigationCommandsHandler.getOrientation(promise);
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
         }
535
         }
536
         promise.resolve(OrientationHelper.getOrientation(currentActivity));
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
 import com.reactnativenavigation.react.ReactDevPermission;
11
 import com.reactnativenavigation.react.ReactDevPermission;
12
 
12
 
13
 public abstract class SplashActivity extends AppCompatActivity {
13
 public abstract class SplashActivity extends AppCompatActivity {
14
+    public static boolean isResumed = false;
14
 
15
 
15
     @Override
16
     @Override
16
     protected void onCreate(@Nullable Bundle savedInstanceState) {
17
     protected void onCreate(@Nullable Bundle savedInstanceState) {
22
     @Override
23
     @Override
23
     protected void onResume() {
24
     protected void onResume() {
24
         super.onResume();
25
         super.onResume();
26
+        isResumed = true;
25
 
27
 
26
         if (NavigationApplication.instance.getReactGateway().hasStartedCreatingContext()) {
28
         if (NavigationApplication.instance.getReactGateway().hasStartedCreatingContext()) {
29
+            NavigationApplication.instance.getEventEmitter().sendAppLaunchedEvent();
30
+            overridePendingTransition(0, 0);
27
             finish();
31
             finish();
28
             return;
32
             return;
29
         }
33
         }
42
         NavigationApplication.instance.startReactContextOnceInBackgroundAndExecuteJS();
46
         NavigationApplication.instance.startReactContextOnceInBackgroundAndExecuteJS();
43
     }
47
     }
44
 
48
 
49
+    @Override
50
+    protected void onPause() {
51
+        super.onPause();
52
+        isResumed = false;
53
+    }
54
+
45
     private void setSplashLayout() {
55
     private void setSplashLayout() {
46
         final int splashLayout = getSplashLayout();
56
         final int splashLayout = getSplashLayout();
47
         if (splashLayout > 0) {
57
         if (splashLayout > 0) {

+ 14
- 0
src/NativeEventsReceiver.js View File

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
   }
162
   }
163
 }
163
 }
164
 
164
 
165
+async function isAppLaunched() {
166
+  return await platformSpecific.isAppLaunched();
167
+}
168
+
165
 export default {
169
 export default {
166
   getRegisteredScreen,
170
   getRegisteredScreen,
167
   registerComponent,
171
   registerComponent,
177
   startSingleScreenApp: startSingleScreenApp,
181
   startSingleScreenApp: startSingleScreenApp,
178
   setEventHandler: setEventHandler,
182
   setEventHandler: setEventHandler,
179
   clearEventHandler: clearEventHandler,
183
   clearEventHandler: clearEventHandler,
180
-  handleDeepLink: handleDeepLink
184
+  handleDeepLink: handleDeepLink,
185
+  isAppLaunched: isAppLaunched
181
 };
186
 };

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

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

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

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

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

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

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

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