Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

app state goes on an infinite loop while listening for changes with appState on android #30206

Open
codeamo opened this issue Oct 18, 2020 · 22 comments

Comments

@codeamo
Copy link

codeamo commented Oct 18, 2020

Description

I have implemented AppState to listen for the app state and then do certain tasks when the app either goes foreground or background. Specifically, I am doing two actions on app state change.

  1. When the app is in the foreground, I am checking if the user may have toggled permission back on so I can react properly.
  2. When the app is in a background mode, I dispatch an action to turn a flag on so when the user opens the app they will be prompted by an authentication screen to verify themselves with biometrics.

The first option works pretty well, but when I added the dispatch action inside the handler to turn the flag on, the app state keeps changing again and again and the app state goes on an infinite loop.

React Native version:

System:
    OS: macOS 10.15.7
    CPU: (8) x64 Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
    Memory: 114.68 MB / 8.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 14.6.0 - /usr/local/bin/node
    Yarn: Not Found
    npm: 6.14.6 - /usr/local/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Managers:
    CocoaPods: 1.9.1 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: iOS 13.6, DriverKit 19.0, macOS 10.15, tvOS 13.4, watchOS 6.2
    Android SDK: Not Found
  IDEs:
    Android Studio: 4.0 AI-193.6911.18.40.6821437
    Xcode: 11.6/11E708 - /usr/bin/xcodebuild
  Languages:
    Java: 14.0.2 - /usr/bin/javac
    Python: 3.7.6 - /Users/amiinamo/.pyenv/shims/python
  npmPackages:
    @react-native-community/cli: Not Found
    react: ~16.11.0 => 16.11.0 
    react-native: ~0.62.2 => 0.62.2 
  npmGlobalPackages:
    *react-native*: Not Found

Steps To Reproduce

Provide a detailed list of steps that reproduce the issue.

  1. Implemented AppState and subscribed to state changes with async function for handling app state change.
  2. Added redux action to the handler.
  3. Apps state keep changing and goes to an infinite loop which even forces the app to open and come back to the foreground while in the background.

Expected Results

I expected the app to only change the state when opening the app, leaving the app, or transitioning between foreground & background which only works for IOS.

Snack, code example, screenshot, or link to a repository:

  const appState = useRef(AppState.currentState);
  const [appStateVisible, setAppStateVisible] = useState(appState.current);

  useEffect(() => {
    AppState.addEventListener('change', checkPermissions);

    return () => {
      AppState.removeEventListener('change', checkPermissions);
    };
  }, []);

  const checkPermissions = async nextAppState => {
    // just an async function that reacts to any permission changes while on background.
    await checkMultiplePermissions(saveMultiplePermissionAccess, permissions);

    if (nextAppState === 'background') {
      // an action to save the app state on redux store when the app goes background. this is the flag
      dispatch(saveAppState(true));
    }
    appState.current = nextAppState;
    setAppStateVisible(appState.current);
  };
@codeamo codeamo changed the title app state goes on an infinite loop with appState! app state goes on an infinite loop while listening for changes with appState! Oct 19, 2020
@codeamo codeamo changed the title app state goes on an infinite loop while listening for changes with appState! app state goes on an infinite loop while listening for changes with appState on android Oct 19, 2020
@codeamo codeamo closed this as completed Oct 24, 2020
@adam-ashored
Copy link

@codeamo I seem to be randomly stuck inside an app state loop - sometimes I'm stuck, sometimes I'm not - in release and debug react-native android builds. Curious, why did you close this ticket?

@shomatz
Copy link

shomatz commented Nov 18, 2020

@codeamo
how do you fix this?

@adam-ashored
Copy link

adam-ashored commented Nov 18, 2020

@shomatz not sure if it helps but for me it was because I was requesting app permissions on Android and every time AppState changed I requested them again. But I wasn’t aware that AppState changes to background while Android is requesting a permission (not the behaviour on iOS).

Once I accounted for this, I fixed my infinite loop.

@codeamo codeamo reopened this Nov 19, 2020
@codeamo
Copy link
Author

codeamo commented Nov 19, 2020

@adam-ashored @shomatz sorry for the late response, but I have closed the issue because of no response. plus I haven't solved this issue yet as I quickly jumped to different tasks. I have reopened the issue so maybe it could be answered and it would help someone in the future.

@info-bit
Copy link

info-bit commented Feb 5, 2021

This seems to be happening in our case as well.
The app goes in an infinite loop only on Android (specifically, it seems to happen only in Android 10), but we already took care of the permissions.
When we remove the event listener the loop never happens.
Also, it happens only when we dispatch a redux action after making a HTTP request inside the handler function.

We are using react-native 0.63.2. We already tried upgrading to the latest version but the issue is still present.

@DracotMolver
Copy link

I'm facing the a similar issue as @info-bit
I'm currently using my physical device with Android 10. In my case, when I show up a WebView, the change event is triggered. Then, after closing the app, the change event is triggered again, but this time in this infinite loop.

I'm using react-native: "0.63.4"
My code is something like this

 const _handleAppStateChange = (nextAppState) => {
  // Running some code... later without stopping
  };

  useEffect(() => {
    AppState.addEventListener('change', _handleAppStateChange);

    return () => {
      AppState.removeEventListener('change', _handleAppStateChange);
    };
  }, []);

My solution was to use the focus event. But, this won't be a solution for some other cases where we do need the change event

@intellimouseftw
Copy link

@DracotMolver I'm not sure if it's okay to do this, I have a question unrelated to this issue, but related to your previous reply.

Did you ever manage to get AppState.addEventListener('focus', _handleFocusChange) working on Android? If so would you mind sharing a code snippet of your implementation (ie. registration of the listener & implementation of the handler)? Can't seem to get it to work, tested with the latest release version (0.64.2) and 0.63

@DracotMolver
Copy link

@intellimouseftw yes, but actually it was caused by another thing away from this. In my case was that I was handle a useEffect with an async callback. fixing that, everything worked perfect. So I changed it back to the onChange event

@arshadazaad3
Copy link

@DracotMolver I'm not sure if it's okay to do this, I have a question unrelated to this issue, but related to your previous reply.

Did you ever manage to get AppState.addEventListener('focus', _handleFocusChange) working on Android? If so would you mind sharing a code snippet of your implementation (ie. registration of the listener & implementation of the handler)? Can't seem to get it to work, tested with the latest release version (0.64.2) and 0.63

const _handleAppStateChange = (nextAppState) => {
isDevEnv() && systemLog(App State - ${nextAppState});
};

useEffect(() => {
    AppState.addEventListener("change", _handleAppStateChange);

    return () => {
        AppState.removeEventListener("change", _handleAppStateChange);
    };
}, []);

@intellimouseftw
Copy link

@DracotMolver I'm not sure if it's okay to do this, I have a question unrelated to this issue, but related to your previous reply.

Did you ever manage to get AppState.addEventListener('focus', _handleFocusChange) working on Android? If so would you mind sharing a code snippet of your implementation (ie. registration of the listener & implementation of the handler)? Can't seem to get it to work, tested with the latest release version (0.64.2) and 0.63

const _handleAppStateChange = (nextAppState) => {
isDevEnv() && systemLog(App State - ${nextAppState});
};

useEffect(() => {
    AppState.addEventListener("change", _handleAppStateChange);

    return () => {
        AppState.removeEventListener("change", _handleAppStateChange);
    };
}, []);

Thanks but I'm looking for a solution to the "focus" event, not the "change" event.

The "change" event works fine.

@arshadazaad3
Copy link

@DracotMolver I'm not sure if it's okay to do this, I have a question unrelated to this issue, but related to your previous reply.

Did you ever manage to get AppState.addEventListener('focus', _handleFocusChange) working on Android? If so would you mind sharing a code snippet of your implementation (ie. registration of the listener & implementation of the handler)? Can't seem to get it to work, tested with the latest release version (0.64.2) and 0.63

const _handleAppStateChange = (nextAppState) => {
isDevEnv() && systemLog(App State - ${nextAppState});
};

useEffect(() => {
    AppState.addEventListener("change", _handleAppStateChange);

    return () => {
        AppState.removeEventListener("change", _handleAppStateChange);
    };
}, []);

Thanks but I'm looking for a solution to the "focus" event, not the "change" event.

The "change" event works fine.

Okay sure
I'll let you know if I find one

@stale
Copy link

stale bot commented Jan 9, 2022

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

@stale stale bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Jan 9, 2022
@jonathanm-tkf
Copy link

I have a similar problem, in my case, if I create apk in release mode, every time I open fresh app the state change to active, it's correct? because my perception of this state changes if it has a previous state, in my scenario I don't have previous state because it is a new open.

Could someone help me on this?

Thanks

@cortinico cortinico removed the Stale There has been a lack of activity on this issue and it may be closed soon. label Jun 1, 2022
@samuelcai-chancetop
Copy link

Our app (RN 0.66.0) is having similar problem, I don't know when it started happen, or what caused it, still investigating, but here is what I observed:
This happened not only on Android, but also on iOS. It is not happening all the time in our app, just some users and sometimes.
I can find two customers based on our last two weeks' log:
Customer 1: Pixel 6, Android 13
Customer 2: iPhone 11, iOS 15.7

@anastasia-iteric
Copy link

@adam-ashored, can you explain how do you accounted "AppState changes to background while Android is requesting a permission (not the behaviour on iOS)."?

@efstathiosntonas
Copy link

@anastasia-iteric if your AppState is "close" to a permissions request eg. check for location permission everytime the app comes to the foreground via AppState then the underlying native request for permissions will put the app in the background for a fraction of a second and then bring it back to the foreground. As you can see, we get an infinite loop here.

@cfradella
Copy link

Experiencing this issue as well, +6 months later. When the app state is a dependency in a useEffect and that useEffect makes permissions requests, it's an infinite loop.

I'd really like to know fundamentally why the device is under the impression backgrounding is occurring, when no permissions model is popping up. It seems like a very common scenario to need to request permissions when foregrounding the app, I'm unsure how others are getting around this. In our case, creating a "has asked" flag isn't going to work.

@keremkurtulus
Copy link

I fixed the problem with this source code below;

const isPermissionFetching = useRef(false);
...
const handlerAppStateChange = async (nextAppState: AppStateStatus) => {
    if (nextAppState === 'active' && !isPermissionFetching.current) {
      console.log('App has come to the foreground!');
      isPermissionFetching.current = true;
      await checkApplicationPermissions();
      isPermissionFetching.current = false;
    }
  };

  useEffect(() => {
    const subscription = AppState.addEventListener(
      'change',
      handlerAppStateChange,
    );

    return () => {
      subscription.remove();
    };
  }, []);
...

@pohsiu
Copy link

pohsiu commented Sep 20, 2023

Issue still existed on "react-native": "0.71.8",

@gabrielgallardoe
Copy link

I have a similar problem on "react-native": "0.72.4" + wix react-native-navigation 7.37

@SebastijanKokai
Copy link

SebastijanKokai commented Jan 5, 2024

Had the same issue, as others have said, Android devices go to background when requesting permission.
With the help of this comment request permission always returns denied I was able to solve the infinite loop, here is my code, hope it helps someone:

const [permissionResponse, setPermissionResponse] = useState<Location.LocationPermissionResponse>();
	const appState = useRef(AppState.currentState);

	useEffect(() => {
		requestPermissionAndExecute(passMethodToBeExecuted);
		const appStateSubscription = AppState.addEventListener('change', handleAppStateChange);

		return () => {
			appStateSubscription.remove();
		}
	}, []);

	const requestPermissionAndExecute = async (callback: () => void) => {
		const permissionResponse = await Location.requestForegroundPermissionsAsync();
		setPermissionResponse(permissionResponse);
		if (permissionResponse.granted) {
			callback();
		}
	}

	const handleAppStateChange = async (nextAppState: AppStateStatus) => {
		if (nextAppState === appState.current) return;

		const isTransitioningToForeground = appState.current.match(/inactive|background/) && nextAppState === 'active';

		if (isTransitioningToForeground) {
			await requestPermissionAndExecute(passMethodToBeExecuted);
		} else {
			// App went to background
		}

		appState.current = nextAppState;
	}

The main problem for me was that I wasn't checking for this conditional
appState.current.match(/inactive|background/), but was doing only this nextAppState === 'active'

Also I had to do await when calling requestPermissionAndExecute(passMethodToBeExecuted) inside handleAppStateChange method.

That fixed it for me

@douglasjunior
Copy link

On Android, the background state means that the React Native Activity is in background, and not necessary the entire app.

To handle this, I created a package that implements the Android Lifecycle API for React Native: https://github.com/douglasjunior/react-native-applifecycle

Why Use This?

The original AppState API provided by React Native behaves differently between Android and iOS, particularly regarding the background state:

  • On iOS, the background state signifies that the entire app is in the background.
  • On Android, the background state indicates that the React Native Activity is in the background, which might not necessarily mean the entire app is in the background.

By using react-native-applifecycle, you can handle these differences seamlessly across both platforms, ensuring that the state background will be dispatched only when the entire app is in background.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests