9

I am trying to change the UI just before the user leaves the app (user is in multi-tasking view or switches to some other app). To be more specific when user leaves the app I want to add a fullscreen view with the app logo.

I am using AppState for that.

On iOS it works as expected: in multitasking view app gets inactive state and once switched to other app state goes to background. When state is inactive I can still change UI.

However, on Android the state is either active or background. Problem is that in background state I cannot change the UI anymore.

Is this a bug on Android? If not, what are my options to get it working on Android.

Thanks.

algizmo
  • 91
  • 1
  • 3
  • 2
    Its not a bug. Android implementation of AppState component has only 'active' and 'background' state. Just curious, why do you want to show a fullscreen view with the app logo when user is about the leave the app? – Jickson Nov 03 '16 at 16:21
  • 3
    I want to show it so that sensitive data is not visible in the multitasking view. I guess I need to dig in to native stuff. – algizmo Nov 04 '16 at 07:10
  • i have a same problem. any help will be appreciated – Emad Bayat Oct 20 '19 at 14:01
  • @algizmo if you want to ensure that no sensitive data is displayed, you should not rely on code - your app might be killed without any chance to execute code. What you should do is to prevent screenshots. This can be done by setting the SECURE flag on the window: https://stackoverflow.com/questions/28606689/how-to-prevent-screen-capture-in-android; but I don't know if react has support for that – Kurt Huwig Feb 13 '20 at 12:47
  • i am stuck with this problem recently. have you found any solution to this? – paperskyline Nov 25 '22 at 04:21

3 Answers3

8

Could you please confirm if my understanding is correct? To simulate the same states as iOS, I can add some code to MainActivity.java to listen to its lifecycle events.

If you need to, you can simulate the same states as iOS by adding some code to MainActivity.java to listen to its lifecycle events

//onResume = 'active'  
//onPause = 'inactive'  
//onStop = 'background' 

@Override
public void onResume() {
    super.onResume();
    ReactContext reactContext = getReactInstanceManager().getCurrentReactContext();
    WritableMap params = Arguments.createMap();
    params.putString("event", "active");

    // when app starts reactContext will be null initially until bridge between Native and React Native is established
    if(reactContext != null) {
        getReactInstanceManager().getCurrentReactContext()
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit("ActivityStateChange", params);
    }
}

@Override
public void onPause() {
    super.onPause();
    ReactContext reactContext = getReactInstanceManager().getCurrentReactContext();
    WritableMap params = Arguments.createMap();
    params.putString("event", "inactive");

    if(reactContext != null) {
        getReactInstanceManager().getCurrentReactContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit("ActivityStateChange", params);
    }
}

@Override
public void onStop() {
    super.onStop();
    ReactContext reactContext = getReactInstanceManager().getCurrentReactContext();
    WritableMap params = Arguments.createMap();
    params.putString("event", "background");

    if(reactContext != null) {
        getReactInstanceManager().getCurrentReactContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit("ActivityStateChange", params);
    }
}

Then in your JS listen to these lifecycle changes using DeviceEventEmitter

const nativeEventListener = DeviceEventEmitter.addListener('ActivityStateChange',
  (e)=>{
      console.log(e.event);
})
Jeff Bootsholz
  • 2,971
  • 15
  • 70
  • 141
Brien Crean
  • 2,599
  • 5
  • 21
  • 46
  • I don't see any difference to this and react native's implementation. It doesn't consider fragments https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java – evanjmg Mar 08 '18 at 16:36
  • It looks different to me; he's emitting "inactive" onPause whereas react native emits "background". He then emits "background" onStop. That said, I tried this and it didn't work / correctly simulate iOS appstate behavior. – cbartondock Apr 02 '18 at 05:07
0

Anyone who is still wondering, onWindowFocusChanged method can help with that task, it handles events when a user moves to Recents screen and opens Notification center, and comes back to a foreground respectively.

Copy that code into your MainActivity.java.

@Override
  public void onWindowFocusChanged(boolean hasFocus) {
    ReactContext reactContext = getReactInstanceManager()
      .getCurrentReactContext();
    WritableMap params = Arguments.createMap();
    if (hasFocus) {
      params.putString("event", "active");
    } else {
      params.putString("event", "inactive");
    }

    if (reactContext != null) {
      getReactInstanceManager()
        .getCurrentReactContext()
        .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
        .emit("ActivityStateChange", params);
    }
  }

Don't forget to have that stuff imported as well:

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

After that React Native code:

useEffect(() => {
    const nativeEventListener = DeviceEventEmitter.addListener(
      'ActivityStateChange',
      e => {
        console.log(e.event)
      },
    );
    return () => {
      DeviceEventEmitter.removeAllListeners();
    }
  }, []);

UPD: It's better to use NativeEventEmitter on RN side:

const NativeEvent = new NativeEventEmitter();

useEffect(() => {
  const handleActiveState = NativeEvent.addListener('ActivityStateChange', (e) => {
        console.log(e.event);
      });
      return () => {
        handleActiveState.remove();
      }
}, []);
-1

For android you can use 'background' instead of 'inactive' like this:

  AppState.addEventListener('change', state => {

  if ( state === 'background') {
    //write your code here
   }

});
Vishal Dhaduk
  • 492
  • 5
  • 13