18

In the React Native AppState library:
iOS has three states background->inactive->active
Android only has background->active

When an Android app is fully backgrounded the MainActivity goes from onPause -> onStop

When there is a System Notification e.g. an In app purchase it goes to onPause

I need to run some code when the app goes from background to the foreground
onStop -> onResume

I don't want it to run if the app was briefly paused because of a system notification
onPause -> onResume

Is this possible? The lifecycle events for React do not have an onHostStop

I tried emitting an event from MainActivity for each Activity lifecycle event but that caused the App to crash with a null pointer exception.

Is it even possible to emit an event to React Native from MainActivity?

Thanks

EDIT Added code to show attempt to emit event from MainActivity

MainActivity.java snippet

import com.facebook.react.ReactActivity;
import com.facebook.react.modules.core.DeviceEventManagerModule;

public class MainActivity extends ReactActivity {

    DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter;

    @Override
    public void onStop() {
        super.onStop();
        eventEmitter.emit("onStop", "ActivityonStop");
    }
}

React Native

const nativeEventListener = DeviceEventEmitter.addListener('onStop',
  (e)=>{
    console.log("NATIVE_EVENT");
    dispatch({type: "NATIVE_EVENT"})
})

error in logcat

E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.realer.android, PID: 15251
java.lang.RuntimeException: Unable to stop activity {com.realer.android/com.realer.android.MainActivity}: java.lang.NullPointerException: Attempt to invoke interface method 'void com.facebook.react.modules.core.DeviceEventManagerModule$RCTDeviceEventEmitter.emit(java.lang.String, java.lang.Object)' on a null object reference
   at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:3837)
   at android.app.ActivityThread.handleStopActivity(ActivityThread.java:3886)
   at android.app.ActivityThread.-wrap25(ActivityThread.java)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1494)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:154)
   at android.app.ActivityThread.main(ActivityThread.java:6077)
   at java.lang.reflect.Method.invoke(Native Method)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'void com.facebook.react.modules.core.DeviceEventManagerModule$RCTDeviceEventEmitter.emit(java.lang.String, java.lang.Object)' on a null object reference
   at com.realer.android.MainActivity.onStop(MainActivity.java:40)
   at android.app.Instrumentation.callActivityOnStop(Instrumentation.java:1289)
   at android.app.Activity.performStop(Activity.java:6839)
   at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:3834)
   at android.app.ActivityThread.handleStopActivity(ActivityThread.java:3886) 
   at android.app.ActivityThread.-wrap25(ActivityThread.java) 
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1494) 
   at android.os.Handler.dispatchMessage(Handler.java:102) 
   at android.os.Looper.loop(Looper.java:154) 
   at android.app.ActivityThread.main(ActivityThread.java:6077) 
   at java.lang.reflect.Method.invoke(Native Method)
Brien Crean
  • 2,599
  • 5
  • 21
  • 46
  • Can you add a log or a more detailed description of the NPE error you got when you emitted events from MainActivity? I've done that in the past for some activity events and it worked fine for me. – Andrei Pitea Dec 01 '16 at 12:11
  • @andreipitea Thanks for the reply! I updated the question above with a snippet from my MainActivity, RN code and logcat error. Any help would be greatly appreciated. Not sure if I am using the eventEmitter.emit function correctly? – Brien Crean Dec 01 '16 at 20:00
  • I think you are looking for [`onStart`](https://developer.android.com/reference/android/app/Activity.html#onStart()), which is the complementary [lifecycle event](https://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle) to `onStop`; just as `onResume` is to `onPause`. [`onRestart`](https://developer.android.com/reference/android/app/Activity.html#onRestart()) is another option depending on your use-case. Though I am not very familiar with the React-Native framework, so these lifecycle events may not be available. – Bryan Dec 01 '16 at 20:31
  • Also, where do you instantiate your `eventEmitter` object? It is not ever instantiated in the code you posted. – Bryan Dec 01 '16 at 20:32
  • You're totally right, sorry I'm not an Android dev obviously! It looks like I might not be able to instantiate that eventEmitter from MainActivity because I need to access the ReactContext and attach the eventEmitter to it I would have to create module to attach an emitter. The issue is that from a module, React native only has access to onResume, onPause and onDestroy. None of which help me – Brien Crean Dec 01 '16 at 21:07

2 Answers2

27

Try updating your MainActivity.java like this:

public class MainActivity extends ReactActivity {

    @Override
    public void onStop() {
        super.onStop();

        WritableMap params = Arguments.createMap(); // add here the data you want to send
        params.putString("event", "ActivityonStop"); // <- example

        getReactInstanceManager().getCurrentReactContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit("onStop", params);
    }
}

Let me know if this works. In my apps I usually send the events from a class that extends ReactContextBaseJavaModule where I can access the context just by calling getReactApplicationContext(), but it seems that it might work if you can obtain the ReactContext from the ReactInstanceManager.

Andrei Pitea
  • 478
  • 4
  • 9
  • Thanks so much! I didn't realise MainActivity has access to `getReactInstanceManager()`. That solution works really well for onPause and onStop, which I think, is all I really need. However when I tried it on onResume the app crashed on start. Maybe React Native is not initialised when the App runs the first onResume? – Brien Crean Dec 03 '16 at 01:37
  • 1
    When the app is first initialized you don't have the `ReactContext` That is being created after the bridge between native and react-native is established. I assume that won't be a big problem for you since you are trying to identify when the app is brought from the background (and not initialized). If this is the case you could just add a null check before trying to use the context. – Andrei Pitea Dec 05 '16 at 12:22
  • 1
    Is there a way to test that the events are being fired properly? – Pranjal Khandelwal Apr 18 '21 at 04:29
  • @PranjalKhandelwal no. I am trying to work out a way for unit testing, we have used system log watching https://stackoverflow.com/questions/1119385/junit-test-for-system-out-println but it's not ideal – Mr Heelis Aug 18 '22 at 08:25
  • So we just need context to emit events? Btw we can access the context by creating a static class and pass the context from the constructor of Module class and thus you can access the context from any class – CrackerKSR Aug 27 '22 at 07:03
1

According to docs

private void sendEvent(ReactContext reactContext,
                       String eventName,
                       @Nullable WritableMap params) {
  reactContext
      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
      .emit(eventName, params);
}

If you are emitting from other class where context is not accessible then you can set Context to static class and then access it from any other classes.

// ContextHolder.java
import com.facebook.react.bridge.ReactApplicationContext;

public class ContextHolder {
  private static ReactApplicationContext reactContext;

  public static ReactApplicationContext getReactContext() {
    return reactContext;
  }

  public static void setReactContext(ReactApplicationContext context) {
   
    ContextHolder.reactContext = context;
  }
}

So instead of reactContext you can simply call ContextHolder.getReactContext()

Don't forget to set context in Module constructor

public class TheModule extends ReactContextBaseJavaModule {

  public TheModule(ReactApplicationContext reactContext) {
    super(reactContext);
    this.reactContext = reactContext;
    ContextHolder.setReactContext(reactContext); // <- set
  ...
CrackerKSR
  • 1,380
  • 1
  • 11
  • 29