4

As the React-Native AppState API is iOS only I'm writing an appState event emitter for the Android side of my app. In the app's MainActivity on the Java side, I want to emit from the onResume and onPause functions to tell the JS side over the bridge that my app is either in the foreground or background

At the moment, I can minimise my app (and go back to the home-screen on my device) and the background event is emitted correctly. However, when I resume my app nothing is fired...also nothing is fired when the app initially opens up.

I've narrowed this down to the fact that in these cases mReactInstanceManager.getCurrentReactContext() is null...for some reason.

Here's my code from MainActivity.java:

@Override
protected void onPause() {
    super.onPause();

    if (mReactInstanceManager != null) {
        mReactInstanceManager.onPause();

        //send the appState back to JS
        Log.d("REACT_STATE", "Paused");  //this always fires
        if(mReactInstanceManager.getCurrentReactContext()!=null) {
            WritableMap params = Arguments.createMap();
            params.putString("currentAppState", "background");
            sendEvent(mReactInstanceManager.getCurrentReactContext(), "appStateChange", params);
        }
    }
}

and,

@Override
protected void onResume() {
    super.onResume();

    if (mReactInstanceManager != null) {
        mReactInstanceManager.onResume(this, this);

        //send the appState back to JS
        Log.d("REACT_STATE", "Resumed"); //this also always fires
        if(mReactInstanceManager.getCurrentReactContext()!=null) {
            WritableMap params = Arguments.createMap();
            params.putString("currentAppState", "foreground");
            sendEvent(mReactInstanceManager.getCurrentReactContext(), "appStateChange", params);
        }
    }
}

and this is the emitter code:

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

The JS side of things is set up OK and working - I can provide this code if it helps. I'm fairly certain my issue lies on this side. Am I trying to emit this at the wrong time? Do I need to move it elsewhere? Any advice is much appreciated!

3 Answers 3

2

Ok, I've managed to sort this. I created a new Plugin Package AppStateAndroid which emits back to the JS. This is added at the right time in the app lifecycle and works perfectly. I'm going to post the code up to GitHub (http://github.com/scgough) but for anyone that's wondering here you go:

public class AppStateAndroidPlugin extends ReactContextBaseJavaModule implements Application.ActivityLifecycleCallbacks {

private static final String PLUGIN_NAME = "AppStateAndroid";

private ReactContext mReactContext;

protected Activity activity = null;

protected Activity getActivity(){
    return this.activity;
}

public AppStateAndroidPlugin(ReactApplicationContext reactContext, Activity activity) {
    super(reactContext);

    this.mReactContext = reactContext;

    this.activity = activity;
    this.activity.getApplication().registerActivityLifecycleCallbacks(this);
}

private void sendEvent(ReactContext reactContext,
                       String eventName,
                       @Nullable WritableMap params) {

    Log.d(PLUGIN_NAME, "Sending Event"+params.toString());
    reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(eventName, params);
}
...
@Override
public void onActivityResumed(Activity activity) {
    Log.d(PLUGIN_NAME, "Resumed");
    if(mReactContext!=null) {
        WritableMap params = Arguments.createMap();
        params.putString("currentAppState", "active");
        sendEvent(mReactContext, "appStateChange", params);
    }
}

@Override
public void onActivityPaused(Activity activity) {
    Log.d(PLUGIN_NAME, "Paused");
    if(mReactContext!=null) {
        WritableMap params = Arguments.createMap();
        params.putString("currentAppState", "background");
        sendEvent(mReactContext, "appStateChange", params);
    }
}
...
@Override
public void onActivityDestroyed(Activity activity) {
    Activity myActivity = this.getActivity();
    if (activity == myActivity){
        myActivity.getApplication().unregisterActivityLifecycleCallbacks(this);
    }
}

@Override
public String getName() {
    return PLUGIN_NAME;
}
}

You'll need a package file for this then in your main activity:

mReactInstanceManager = ReactInstanceManager.builder()
            .setApplication(getApplication())
            .setBundleAssetName("index.android.bundle")
            .setJSMainModuleName("index.android")
            .addPackage(new MainReactPackage())
            .addPackage(new AppStateAndroidPluginPackage(this))
            ...
Sign up to request clarification or add additional context in comments.

3 Comments

@Srikanth thanks. Glad you like. Up vote etc please! :-)
I upvoted it.. I have a Question if u can help me please.. I have a menu button and onclick() of the menu button I need to send the event
@Srikanth best to post your question in it's own post with code fully explaining the issue
1

GitHub: Is there any callback for android when ReactContext is created in MainAcitivity? #3887

You could avoid this by listening to the onReactContextInitialized callback.

@Override
public void onResume() {
    super.onResume();
    getReactInstanceManager().addReactInstanceEventListener(this);
}  

@Override
public void onPause() {
    super.onPause();
    getReactInstanceManager().removeReactInstanceEventListener(this);
}  

When your ReactContext is created, the callback will be notified:

@Override
public void onReactContextInitialized(ReactContext context) {
    Log.d(TAG, "Here's your valid ReactContext");
}  

OR:

reactInstanceManager.addReactInstanceEventListener(context -> {
    // first, grab the current context
    String eventName = "RevLoadPluginModulePathsEvent";

    // a WritableMap is the equivalent to a JS Object:
    // the React native bridge will convert it as is
    WritableMap params = Arguments.createMap();
    params.putString("MyCustomEventParam", "REV LOAD MODULES . . . .");

    revInitReactActivity.revSendReactEvent(context, eventName, params);
});  

Comments

0

For anyone looking for ways to get the ReactContext instance from Activity, using

 this.getReactNativeHost().getReactInstanceManager().getCurrentReactContext();

instead of

 this.getReactInstanceManager().getCurrentReactContext();

Although I don't understand why...

3 Comments

This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
@anticafe I believe this is exactly an answer to the question. The problem in the question was not being able to getting the ReactContext instance from the activity. This solves it.
@user2718861 getReactNativeHost() does not exist inside an Activity. Where would you call this?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.