How to use Blur event for AppState in react native - react-native

Please I need an example on how to use the 'blur' event for react native AppState. I am trying to respond to when the app is not in focus e.g. when the user pulls the notification drawer but I keep getting the error message Invariant Violation: Trying to subscribe to unknown event: "blur".

Based on the tags associated with the commit that this feature landed in (https://github.com/facebook/react-native/commit/d45818fe47c53a670db933cf805910e227aa79c9) it seems like that is only available starting in 0.61 and hasn't landed in a stable release yet. Make sure you're running 0.61.0-rc.0 or later.

According to documentation . Blur is [Android only]
"[Android only] Received when the user is not actively interacting with the app. Useful in situations when the user pulls down the notification drawer. AppState won't change but the blur event will get fired."
if you still want to use it for android you can use it with condition for android only
import { Platform } from "react-native";
........
componentDidMount() {
if (Platform.OS === "android") {
AppState.addEventListener("blur", this._handleAppStateBlur);
}
}
componentWillUnmount() {
if (Platform.OS === "android") {
AppState.removeEventListener("blur", this._handleAppStateBlur);
}
}
_handleAppStateBlur = () => {
console.log("blur");
};

According to the docs mentioned in the official react native documentation, there are three states supported by AppState:
active - The app is running in the foreground.
background - The app is running in the background. The user is either:
in another app
on the home screen
[Android] on another Activity (even if it was launched by your app)
[iOS] inactive - This is a state that occurs when transitioning between foreground & background, and during periods of inactivity such as entering the Multitasking view or in the event of an incoming call.
Since there is no such state as blur, therefore you are facing an error saying that it could not find such event.
Edit
You have to register blur as an event in your component lifecycle, but you have to be cautious here and have to determine the Platform before registering blur event as it is available in android only and not in ios.
To register an event you have to do this:
import React from 'react';
import {AppState} from 'react-native';
class HandlingEvents extends React.Pure.Component {
constructor(props) {
super(props)
// your state goes here...
}
componentDidMount() {
// your event will be registered here, when your component is mounted on // the screen.
// Be cautious here, make a platform check here so as to avoid discrepancies in ios devices
AppState.addEventListener('blur',this.handleBlurState)
}
componentWillUnMount() {
// your event will be removed here, when your component gets unmounted from the screen.
// Be cautious here, make a platform check here so as to avoid discrepancies in ios devices
AppState.removeEventListener('blur',this.handleBlurState)
}
handleBlurState = (nextAppState) => {
//this method will contain your entire logic, as to how you want to treat your component in this event.
// As per the docs, since the state of your app will not changed, therefore you can continue your logic here by checking if the state of your app is **change** or not..
if (AppState.currentState === "active" && nextAppState === "active") {
//whatever task you want to perform here..;
}
}
}

Related

Remove listener AppState change react native

Good evening everyone, I am facing a problem.
I am developing an app in react-native and I need that, every time a user sends the app in the background or in an inactive state, when he returns to the app I force him to go to a certain screen (Loading) where I perform certain checks (such as if he is a blocked user, deleted, etc ...).
I have now written the following function
const [appState, setAppState] = useState(AppState.currentState);
useEffect(() => {
getAttivita();
getBanner();
const appStateListener = AppState.addEventListener(
"change",
(nextAppState) => {
setAppState(nextAppState);
if (nextAppState === "active") {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: Routes.Loading }],
})
);
}
}
);
return () => {
appStateListener?.remove();
};
}, []);
I put this listener in the Screen Diary (which represents my home).
Now if from the screen Diary, I minimize the app, then I have no problems and everything works as it should.
However if I go to another screen and minimize the app, then I get the following error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in Diary (at SceneView.tsx:122)
Then when I log back into the app I realize that the listener for the app status is still active (so it is as if the remove () had not worked) and in fact I am pushed back into my loading screen.
So I'm wondering, is it the listener that isn't actually being removed?
Or am I doing something wrong?
Thanks in advance to who will answer me .

React Native doesn't re-render when re-opening app

I have a functional component in my react native app that has an event listener watching the app state in order to clear out a cache when the app is closed. This works as expected, and I have logic written in a useEffect block to add/remove this event listener when the app is closed.
The functionality works perfectly when the user either navigates elsewhere in the app or closes the app, but when the app is re-opened after closing (not fully shutting down the app, just moving to a different app) the page is not re-rendered and so the event listener is not re-added.
I've attached the code in my useEffect below.
useEffect(() => {
if (!listenerRef.current) {
toggleEventListener(true);
}
return () => {
toggleEventListener(false);
};
}, []);
You can use appState from react-native to see foreground and background events
https://reactnative.dev/docs/appstate

React Native - Global BackHandler behavior depends on registration timing

I'm migrating an old React Native app from 0.57 to 0.62, and just setup navigation with the current React Navigation package. That app contains a global navigation store that registers to Android back button presses and allows me to intercept back operations, no matter where they originate (hardware button or programmatically).
I have a rather weird behavior here, and it seems to be timing-related. In the snippet below, I register a listener with BackHandler, which supresses back button taps and logs a warning. I'll register it in the componentDidMount method.
export class RootComponent extends React.Component {
private initBackButton() {
const onBackPress = () => {
console.warn("BACK BUTTON SUPPRESSED");
return true;
};
BackHandler.addEventListener("hardwareBackPress", onBackPress);
}
public componentDidMount() {
// TODO register back listener
}
public render() {
return (
<NavigationContainer >
<StackNavigatorSetup />
</NavigationContainer>
);
}
}
If I register the listener synchronously, the back button listener fires if I press it at the initial screen of my StackNavigator.
If I navigate to a second screen, the listener does not fire if I press the back button, and I can return back to the start screen. If I press back there again, the listener fires.
Note that I'm declaring the listener in the root component, so that component isn't going anywhere.
public componentDidMount() {
// only works on the start screen
this.initBackButton();
}
Now, the behavior is different if I use a delay:
If I register the same handler with a delay of 1 second, the handler works on any screen
This is not thread-related. If I use a delay that is too short, it again only works on the start screen
public componentDidMount() {
// works on every screen i'll navigate to
PromiseUtil.delay(1000).then(() => this.initBackButton());
}
To be honest, I don't really have a clue what's happening here. BackHandler seems to be ready, but I don't understand why the listener works either global or not depending on the delay. Also, my root component doesn't really change, so I wonder whether React Navigation is messing with me here...

Expo.js: Capture event when my app goes into the background?

If I'm using my app and then switch out of it using the home button, app switcher, etc, Is there a way to reliably run some code when this event is detected? I want to do some tasks such as cancelling timers, scheduling notifications, and so on.
You can track app's state using AppState provided by 'react-native'
By using AppState.currentState you will know whether the app is in foreground, background, active or inactive. If you want to perform some tasks whenever the state changes you can use event listeners provided by AppState.
You can add the following logic in your Component to get it done -
componentDidMount() {
AppState.addEventListener('change', this.handleStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleStateChange);
}
handleStateChange will be called every time the app moves from foreground to background or vice-versa with the app's current state passed as an argument.
For further reference see the docs
Hope it helped.

How To Use both 'adjustPan' and 'adjustResize' for 'windowSoftInputMode' in React Native Android app

How can I use both 'adjustPan' and 'adjustResize' in AndroidManifest.xml react native app.
Use Case
My navigation is made upon ReactNavigation with StackNavigator and TabNavigator. I have a text box where the user can type any data. While performing this, the tab bar is displaying on the top of Keyboard. In order to block this i used 'adjustPan' and it worked fine.
On another screen, I have a registration with multiple text boxes. Here I cant scroll the entire screen unless and clicking 'tick' on the keyboard or manually click system back button. To solve this issue I found 'KeyboardAvoidingView' which is working fine. but to activate this need to change 'windowSoftInputMode' to 'adjustResize'.
In documentation, found that these two have entirely different property and I can't both together. could someone help me on this?
References:https://medium.freecodecamp.org/how-to-make-your-react-native-app-respond-gracefully-when-the-keyboard-pops-up-7442c1535580
I found an npm package called react-native-android-keyboard-adjust, which allows us to switch the windowSoftInputMode on demand, this should be able to cater for your use case. However, the library seems to be not actively maintained and the installation documentation is a little bit out of date but for the most part, you can follow the instructions given by the README.md.
For the Update MainActivity.java in your project part, the recent versions of React Native should be able to auto-link the dependencies and there is no need to do this modification manually.
After the above steps, you can try to start your app. If you encountered an error related to something like The number of method references in a .dex file cannot exceed 64k, you can add the followings to your android/app/build.gradle file
android {
...
defaultConfig {
...
multiDexEnabled true
}
...
}
After installing the package, you can call the methods provided by the library to change the windowSoftInputMode as you need.
For example, assuming you have a default windowSoftInputMode of adjustResize, and you want to use adjustPan within ScreenA, you can call AndroidKeyboardAdjust.setAdjustPan() when ScreenA mount, and reset the windowSoftInputMode to adjustResize on unmount by calling AndroidKeyboardAdjust.setAdjustResize()
As of 2023, the best choice is react-native-avoid-softinput. react-native-android-keyboard-adjust isn't supported anymore.
You can use AvoidSoftInput.setAdjustPan and AvoidSoftInput.setAdjustResize.
I use custom hook to disable my default behavior on some screens.
import { useCallback } from 'react'
import { AvoidSoftInput } from 'react-native-avoid-softinput'
import { useFocusEffect } from '#react-navigation/native'
import { Platform } from 'react-native'
function useAndroidKeyboardAdjustNothing() {
useFocusEffect(
useCallback(() => {
if (Platform.OS === 'android') {
AvoidSoftInput.setAdjustNothing()
AvoidSoftInput.setEnabled(true)
}
return () => {
if (Platform.OS === 'android') {
AvoidSoftInput.setEnabled(false)
AvoidSoftInput.setAdjustResize()
}
}
}, []),
)
}