react-native icon indicating copy to clipboard operation
react-native copied to clipboard

AppState change listener called multiple times on Android

Open diazweb opened this issue 4 years ago • 20 comments

React Native version:

React Native Environment Info:
    System:
    OS: macOS 10.14.5
    CPU: (8) x64 Intel(R) Core(TM) i7-3615QM CPU @ 2.30GHz
    Memory: 197.04 MB / 16.00 GB
    Shell: 5.3 - /bin/zsh
    Binaries:
    Node: 10.15.3 - /usr/local/bin/node
    Yarn: 1.16.0 - /usr/local/bin/yarn
    npm: 6.9.0 - ~/.npm-global/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
    iOS SDK:
        Platforms: iOS 12.2, macOS 10.14, tvOS 12.2, watchOS 5.2
    Android SDK:
        API Levels: 23, 25, 26, 27, 28
        Build Tools: 26.0.2, 26.0.3, 27.0.0, 27.0.2, 27.0.3, 28.0.0, 28.0.0, 28.0.3
        System Images: android-27 | Google APIs Intel x86 Atom, android-27 | Google Play Intel x86 Atom
    IDEs:
    Android Studio: 3.3 AI-182.5107.16.33.5314842
    Xcode: 10.2.1/10E1001 - /usr/bin/xcodebuild
    npmPackages:
    react: 16.8.3 => 16.8.3
    react-native: 0.59.8 => 0.59.8
    npmGlobalPackages:
    create-react-native-app: 1.0.0
    react-native-cli: 2.0.1
    react-native-git-upgrade: 0.2.7

Steps To Reproduce

  1. Open the app
  2. Press home button
  3. Open the app. You should see the alert once (this is fine)
  4. Press home button
  5. Open the app. The alert will be shown 2 times (this is not fine, it should be only once)
  6. Press home button
  7. Open the app. The alert will be shown 3 times (this is not fine, it should be only once) ...

Expected behaviour: The alert should be shown only once each time you open the app. This issue is happening only in Android. The alert is just an example, you can put anything inside and it will be called multiple times.

Simple code to reproduce:

import React, { Component } from "react";
import { AppState, Text } from "react-native";

export default class App extends Component {
  state = {
    appState: AppState.currentState
  };

  componentDidMount() {
    AppState.addEventListener("change", this._handleAppStateChange);
  }

  componentWillUnmount() {
    AppState.removeEventListener("change", this._handleAppStateChange);
  }

  _handleAppStateChange = nextAppState => {
    if (
      this.state.appState.match(/inactive|background/) &&
      nextAppState === "active"
    ) {
      alert("I should be shown only once");
    }
    this.setState({ appState: nextAppState });
  };

  render() {
    return <Text>Current state is: {this.state.appState}</Text>;
  }
}

diazweb avatar May 26 '19 10:05 diazweb

@diazweb Hey,

I've tried to reproduce your issue with the given code. I get the proper one alert every time (tested on emu and real dev).

What device are you testing that on?

krizzu avatar May 29 '19 08:05 krizzu

@Krizzu Hi,

I've tried the app in:

  • Simulator Nexus 4 API 27 (8.1) with Google Apis x86
  • Real device: BQ Aquaris E5 with Android 6.0.1

I just updated to React Native 0.59.9 and still the same issue, I'm going to update my node.js and JDK, update all the Android Studio packages and reinstall node_modules (just in case). EDIT: I've done this and still the same issue.

Thanks!

diazweb avatar Jun 06 '19 11:06 diazweb

I have the same problem in Honor 9 STF-AL10 with Android 9. but I observed AppState change listener called multiple times occur in exit App by double-click the Android Back button. AppState change listener only called one time if I exit App by click the Home button.

Koppel-Zhou avatar Jun 10 '19 07:06 Koppel-Zhou

I met the similar issue when I was trying to receive share intent with my app. New activity was created every time when the app was opened by intent and AppState listener was called the same number of times as the number of existing activities. Changing android:launchMode to "singleTask" in AndroidManifest.xml fixed the issue for me. But I don't know if your cases are the same as mine.

kazyk avatar Jul 08 '19 16:07 kazyk

Also experiencing this behavior with React 0.59.

@diazweb have you tried 0.60 to see if it still repros this bug? Also, have you tried @kazyk 's solution of changing android:launchMode?

harking avatar Aug 28 '19 17:08 harking

Having same issues via emulator, API 29. Also happens on physical device, Note 10+ running Android 10, API 29.

changing android:launchMode did not help.

only on Android not iOS

YaarPatandarAA avatar Nov 22 '19 19:11 YaarPatandarAA

Pay attention to the native libs you're using and the methods you're calling. I fell on this issue while calling a native module method that was secretely spawning a new hidden activity. The problem right now is that I don't know how to distinguish between having a backgrounded app and having an activity spawned by my app, if I only could reach somehow the current activity name...

I'm sure many issues, like this #18575, are related

giacomocerquone avatar Dec 10 '19 12:12 giacomocerquone

I had this issue and I think it was because the change listener was never being unregistered in componentWillUnmount because componentWillUnmount was never called. Fixed it with the solutoin here:

https://stackoverflow.com/questions/54482733/react-native-appstate-addeventlistener-registering-duplicate-events-on-resume-w/54484054#54484054

if (AppState._eventHandlers.change.size === 0) {
  AppState.addEventListener('change', this.handleAppStateChange)
}

Now I have this typescript error:

src/screens/MapScreen/index.tsx:108:18 - error TS2339: Property '_eventHandlers' does not exist on type 'AppStateStatic'.

@diazweb Hey,

I've tried to reproduce your issue with the given code. I get the proper one alert every time (tested on emu and real dev).

What device are you testing that on?

I also get the alerts twice on my android devices.

biranchi2018 avatar Dec 27 '19 10:12 biranchi2018

Also experiencing this behavior with React 0.59.

@diazweb have you tried 0.60 to see if it still repros this bug? Also, have you tried @kazyk 's solution of changing android:launchMode?

I am using RN 0.60.4, and the bug still exists.

biranchi2018 avatar Dec 27 '19 10:12 biranchi2018

Do you all have the following code ever called during your testing?

  componentWillUnmount() {
    AppState.removeEventListener("change", this._handleAppStateChange);
  }

Linking.addEventListener('url', this._handleOpenURL);

Also triggers multiple times.

msaqlain avatar Feb 07 '20 11:02 msaqlain

I had this issue and I think it was because the change listener was never being unregistered in componentWillUnmount because componentWillUnmount was never called. Fixed it with the solutoin here:

https://stackoverflow.com/questions/54482733/react-native-appstate-addeventlistener-registering-duplicate-events-on-resume-w/54484054#54484054

if (AppState._eventHandlers.change.size === 0) {
  AppState.addEventListener('change', this.handleAppStateChange)
}

Now I have this typescript error:

src/screens/MapScreen/index.tsx:108:18 - error TS2339: Property '_eventHandlers' does not exist on type 'AppStateStatic'.

YOU ARE A KING , SIR , THANK YOU, LIFE SAVING COMMENT

raffoulfrancis avatar Feb 20 '20 16:02 raffoulfrancis

Just a warning, using AppState._eventHandlers.change.size might cause crashes on iOS 13. Seems to be working fine on Android.

ignasbol avatar Apr 16 '20 22:04 ignasbol

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[bot] avatar Oct 12 '20 02:10 stale[bot]

I resolved it by changing arrow function to function in this way:

AppState.addEventListener('change', myFunc) function myFunc() { // some code}

MahmonirB avatar Oct 26 '20 07:10 MahmonirB

I resolved it by changing arrow function to function in this way:

AppState.addEventListener('change', myFunc) function myFunc() { // some code}

Thank you @MahmonirB , For anyone looking for answers, just check if you've used arrow function in the handler function. This is what was the source of my problem as well. No sooner did I change this to a normal function, a whole lot of my problems went away.

devashishsethia avatar Nov 22 '20 18:11 devashishsethia

This is still happening, adding the ref check does fix this, but it does also complains about the missing type.

 if (AppState._eventHandlers.change.size === 0) {
      AppState.addEventListener('change', handleFocusChange)
    }

MorenoMdz avatar Aug 23 '21 19:08 MorenoMdz

Not solve issue, but less callbacks...

  • Real device iPhone 13 - IOS 15.5
  • Expo 43
  • RN 0.64.3
const [appState, setAppState] = useState();

 // APPSTATE HANDLER
  const appHandler = (changeType) => {
    if (changeType === "active" || changeType === "background") {
      console.log("appHandler =>", changeType);
      setAppState(() => changeType);
    }
};

// APPSTATE LISTENER
  useEffect(() => {
    AppState.addEventListener("change", appHandler);
    return () => AppState.removeEventListener("change", appHandler);
  }, []);
  
// APPSTATE CALLBACKS
useEffect(() => {
    const isForeground = appState === "active";
    const isBackground = appState === "background";
    
    // YOUR CODE HERE...

}, [appState]);

vinaciotm avatar Jul 21 '22 04:07 vinaciotm

I had this issue and I think it was because the change listener was never being unregistered in componentWillUnmount because componentWillUnmount was never called. Fixed it with the solutoin here:

https://stackoverflow.com/questions/54482733/react-native-appstate-addeventlistener-registering-duplicate-events-on-resume-w/54484054#54484054

if (AppState._eventHandlers.change.size === 0) {
  AppState.addEventListener('change', this.handleAppStateChange)
}

Now I have this typescript error:

src/screens/MapScreen/index.tsx:108:18 - error TS2339: Property '_eventHandlers' does not exist on type 'AppStateStatic'.

I had this issue and I think it was because the change listener was never being unregistered in componentWillUnmount because componentWillUnmount was never called. Fixed it with the solutoin here:

https://stackoverflow.com/questions/54482733/react-native-appstate-addeventlistener-registering-duplicate-events-on-resume-w/54484054#54484054

if (AppState._eventHandlers.change.size === 0) {
  AppState.addEventListener('change', this.handleAppStateChange)
}

Now I have this typescript error:

src/screens/MapScreen/index.tsx:108:18 - error TS2339: Property '_eventHandlers' does not exist on type 'AppStateStatic'.

When there are two 'change' listeners registered back and forth with same condition to determine add it or not, e.g.

if  (AppState._eventHandlers.change.size === 0) 
{ 
   AppState.addEventListener('change', handleListenerOne) ...
}
...
if  (AppState._eventHandlers.change.size === 0) 
{ 
   AppState.addEventListener('change', handleListenerTwo) ...
}

the first listener make AppState._eventHandlers.change.size becomes 1 and the latter one will never be added.

A workaround could be by adding a boolean to AppState._eventHandlers.change instead, e.g.

if  (!AppState._eventHandlers.change._isListenerOneSubscripted) 
{ 
   AppState.addEventListener('change', handleListenerOne) ...
  AppState._eventHandlers.change._listenerOneSubscripted = true
}
...
if  (!AppState._eventHandlers.change._isListenerTwoSubscripted) 
{ 
   AppState.addEventListener('change', handleListenerTwo) ...
  AppState._eventHandlers.change._listenerTwoSubscripted = true
}

nathanwkwong avatar Aug 23 '22 02:08 nathanwkwong