Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

App state in react-native does not remove the listener

I have added the listener in the following way(tried putting in both constructor and componentDidMount): AppState.addEventListener('change', this._handleAppStateChange);

And the removed the listener the following way in componentWillUnmount method:

AppState.removeEventListener('change', this._handleAppStateChange);

And in the callback function:

  _handleAppStateChange = (nextAppState) => {
    setTimeout(() => {
      alert('App state: ' + this.state.appState);
      alert('Next App state: ' + nextAppState);
    }, 0);
  }

It alerts several times. Its not removing the listener configured once. Please let me know if someone is aware of it ?

like image 551
gbhati Avatar asked May 28 '18 11:05

gbhati


4 Answers

According to the latest docs (September 2021 v0.65+) removeEventListener is deprecated

The docs now recommend using the remove function on the subscription object (EmitterSubscription) which is returned from AppState.addEventListener.

Example usage:

const subscription = AppState.addEventListener('change', (appState) => {
  if (appState !== 'active') {
    return;
  }

  // Run custom logic

  subscription.remove();
});
like image 156
John Kalberer Avatar answered Sep 21 '22 22:09

John Kalberer


You should use API like now on

useEffect(() => {
 const myListener = AppState.addEvenListener('change', this.someHandler)
 return () => {
   myListener.remove()
 }
}, [])
like image 27
Yadigar ZENGİN Avatar answered Sep 20 '22 22:09

Yadigar ZENGİN


Faced the same issue these last days. I've finally managed it by deporting my app state management to my App.js component and created a service manager.

Here quickly how my App.js look like:

import {AppState } from "react-native";
import {AppStateService} from "YOUR_PATH_TO_THE_NEXT_FILE";

export default function App() {

    // Listen to app state
    AppStateService.init();
    useEffect(() => {
        AppState.addEventListener('change', AppStateService.getInstance().handleAppStateChange);
        return (() => {
            AppState.removeEventListener('change', AppStateService.getInstance().handleAppStateChange);
        })
    }, []);

    return (/*Rendering stuff (navigation, error boundary, ...*/);
}

AppStateService.js:

/**
 * Class to allow us to refer to the app state service
 */

export class AppStateService {

    static instance;

    static STATE_ACTIVE         = 'active';
    static STATE_INACTIVE       = 'inactive';
    static STATE_BACKGROUND     = 'background';
    static STATE_NOT_LAUNCHED   = 'not_launched';

    previousState   = AppStateService.STATE_NOT_LAUNCHED;
    currentState    = AppStateService.STATE_ACTIVE;

    handlers = {};

    appLaunchId = 0;

    /**
     * @returns {AppStateService}
     */
    static getInstance() {
        if(!this.instance){
            this.instance = new AppStateService();
        }

        return this.instance;
    }

    static init = () => {
        // This func need to be call in the App.js, it's just here to create the instance
        const instance = AppStateService.getInstance();

        instance.appLaunchId = new Date().getTime() / 1000;
    }

    handleAppStateChange = (nextState) => {
        if(nextState !== this.currentState) {
            this.previousState = this.currentState;
            this.currentState = nextState;

            for (const [key, handler] of Object.entries(this.handlers)) {
                handler(nextState);
            }
        }
    }

    getCurrentState = () => {
        return this.currentState;
    }

    getPreviousState = () => {
        return this.previousState;
    }

    addStateHandler = (key, handler) => {
        this.handlers[key] = handler;
    }

    hasStateHandler = (key) => {
        if( this.handlers[key] ){
            return true;
        }

        return false;
    }

    removeStateHandler = (key) => {
        delete this.handlers[key];
    }
}

and now here is how to call it from anywhere you want in your app components:

export default class RandomComponent extends React.Component {
    
    componentDidMount() {
        // Check app going background or not
        this.handleAppStateChange = this.handleAppStateChange.bind(this);
        AppStateService.getInstance().addStateHandler('myListenerCustomKey', this.handleAppStateChange);
    }

    componentWillUnmount() {
        // Remove app state change listener
        AppStateService.getInstance().removeStateHandler('myListenerCustomKey');
    }

    handleAppStateChange = (nextAppState) => {
        console.log("I'm going to be -" + nextAppState + "- while I was -" + AppStateService.getInstance().getPreviousState() + "-");
    }
}

This way you have the capability to listen everywhere in your app to the app foreground/inactive/background state and correctly subscribe/unsubscribe to those events.

like image 37
PoulsQ Avatar answered Sep 19 '22 22:09

PoulsQ


I'm assuming you're using a class component by the looks of your code. The way I ended up solving it was by creating a pointer function that points to the actual function inside the scope of this, without using .bind(this).

E.g.

// Actual Function    
handleAppStateChange(state) {
  // Work your magic!
}

// Pointer
handleAppStateChangeCall = (state) => this.handleAppStateChange(state);

// Setup listener event
setupAppStateListener() {
  AppState.addEventListener("change", this.handleAppStateChangeCall);
}

// Clear listener event
clearAppStateListener() {
  AppState.addEventListener("change", this.handleAppStateChangeCall);
}

// Mounted Hook
componentDidMount() { 
  setupAppStateListener();
}

// Unmount Hook
componentWillUnmount() {
  clearAppStateListener()
}
like image 43
Alexander Furre Avatar answered Sep 19 '22 22:09

Alexander Furre