Brien Crean
Brien Crean

Reputation: 2648

React Native - how can I emit an event to RN from MainActivity.java?

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)

Upvotes: 19

Views: 18086

Answers (2)

CrackerKSR
CrackerKSR

Reputation: 1877

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
  ...

Upvotes: 1

Andrei Pitea
Andrei Pitea

Reputation: 478

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.

Upvotes: 27

Related Questions