Reputation: 5252
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!
Upvotes: 4
Views: 5625
Reputation: 6624
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);
});
Upvotes: 1
Reputation: 57
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...
Upvotes: 0
Reputation: 5252
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))
...
Upvotes: 2