React-Native : change state when app is in background - react-native

I create a simple app that using React Native AppState:
import React, {Component} from 'react'
import {AppState, Text , View} from 'react-native'
export default class AppStateExample extends React.Component {
constructor(props){
super(props);
this.state = {
name:'not change'
}
}
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextAppState) => {
if(AppState.currentState=='background'){
console.log('background mode');
this.setState({name:'back'});
}
if(AppState.currentState =='active'){
//...
}
};
render() {
return (
<View>
<Text>State Name : {this.state.name}</Text>
</View>
);
}
}
And when I try switch app from foreground to background and then background to foreground console.log('background mode'); work very well and console
print 'background mode'
BUT
The this.setState({name:'back'}); not working and I see 'not change' text in view

Actually, based on React Native Docs on AppState change for Functional Component I prefer to write code like below:
import { useRef, useState, useEffect } from "react";
import { AppState } from "react-native";
const AppStateManager = () => {
const appState = useRef(AppState.currentState);
const [appStateVisible, setAppStateVisible] = useState(appState.current);
useEffect(() => {
AppState.addEventListener("change", handleAppStateChange);
return () => {
AppState.removeEventListener("change", handleAppStateChange);
};
}, []);
const handleAppStateChange = (nextAppState) => {
if (
appState.current.match(/inactive|background/) &&
nextAppState === "active"
) {
console.log("App has come to the foreground!");
}
appState.current = nextAppState;
setAppStateVisible(appState.current);
console.log("AppState", appState.current);
};
return null;
};
export default AppStateManager;
Surely, we can use this component in the root of the project just like a React Component:
~~~
<App>
~~
<AppStateManager />
~~
.
.
.

It is because, AppState.addEventListener('change', this._handleAppStateChange); is too late to register.
You probably want to listen AppState the first thing in your app before main components get loaded and pass down the values probably by your state management library

I would go for a switch that wrap all endPoint
note: to get appState status AppState.currentState
this.state = {
appState: AppState.currentState
// https://facebook.github.io/react-native/docs/appstate.html
};
componentWillMount() {
AppState.addEventListener('change', () => this._handleAppStateChange());
};
componentWillUnmount() {
AppState.removeEventListener('change', () => this._handleAppStateChange());
}
_handleAppStateChange() {
// https://facebook.github.io/react-native/docs/appstate.html
const {
appState
} = this.state;
console.warn({
appState
})
this.fetchData().catch(error => error);
switch (appState) {
case 'active': //The app is running in the foreground
this.onStart();
break;
case 'background': // The app is running in the background. The user is either
this.onEnd();
console.warn('background');
break;
case 'inactive':
// The app transitioning between foreground & background or entering the Multitasking view or in the event of an incoming call
console.warn('Inactive');
break;
default:
console.warn('_handleAppStateChange default');
}
}

Related

How do I go back in webview? I am using the react-navigation package in react-native

I installed the react-navigation package in react-native
I have implemented tab navigation and one of them is implemented in webview format.
My problem is that if I press the back physical button on Android, I go from the app itself to the previous tab, not back from the webview.
I've already applied the back button for the webview on the internet, but I have not done that.
I tried to display the onNavigationStateChange log when debugging, but it was not updated when url was moved after it was loaded at first startup. Here is the code I implemented:
import React from "react";
import {BackHandler} from "react-native";
import {WebView} from "react-native-webview";
class SermonScreen extends React.Component {
constructor(props) {
super(props);
}
static navigationOptions = {
header: null
};
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
_onNavigationStateChange(navState) {
console.log(navState);
this.setState({
canGoBack: navState.canGoBack
});
}
handleBackButton = () => {
console.log(this.state);
if (this.state.canGoBack === true) {
this.webView.goBack();
return true;
} else {
return false;
}
};
render() {
return (
<WebView
source={{uri: 'https://m.youtube.com/channel/UCw3kP3qCCF7ZpLUNzm_Q9Xw/videos' }}
ref={(webView) => this.webView = webView}
onNavigationStateChange={this._onNavigationStateChange.bind(this)}
/>
);
}
}
export default SermonScreen;
Following the official webview documnentation you could try to do this: https://github.com/react-native-community/react-native-webview/blob/master/docs/Guide.md#intercepting-hash-url-changes
In general you were almost there, however the way the YT navigation works made it impossible to be caught via the onNavigationStateChange, that's why we inject a JS code that intercepts these hash changes and posts a message to the parent component, we then catch it inside the onMessage handler and set the state variable properly. Copying the injectedJavaScript and onMessage properties to your example should solve your problem.
I prepared a component for you that seems to do what is needed:
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow
*/
import React, { Fragment } from "react";
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
BackHandler,
StatusBar
} from "react-native";
import { WebView } from "react-native-webview";
import {
Header,
LearnMoreLinks,
Colors,
DebugInstructions,
ReloadInstructions
} from "react-native/Libraries/NewAppScreen";
class App extends React.Component {
constructor(props) {
super(props);
this.startingUrl =
"https://m.youtube.com/channel/UCw3kP3qCCF7ZpLUNzm_Q9Xw/videos";
this.handleBackButton = this.handleBackButton.bind(this);
}
componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener("hardwareBackPress", this.handleBackButton);
}
handleBackButton = () => {
console.log(this.state);
const { canGoBack } = this.state;
if (canGoBack) {
this.webView.goBack();
return true;
} else {
return false;
}
};
render() {
return (
<Fragment>
<WebView
source={{ uri: this.startingUrl }}
style={{ marginTop: 20 }}
ref={webView => (this.webView = webView)}
injectedJavaScript={`
(function() {
function wrap(fn) {
return function wrapper() {
var res = fn.apply(this, arguments);
window.ReactNativeWebView.postMessage('navigationStateChange');
return res;
}
}
history.pushState = wrap(history.pushState);
history.replaceState = wrap(history.replaceState);
window.addEventListener('popstate', function() {
window.ReactNativeWebView.postMessage('navigationStateChange');
});
})();
true;
`}
onMessage={({ nativeEvent: state }) => {
if (state.data === "navigationStateChange") {
// Navigation state updated, can check state.canGoBack, etc.
this.setState({
canGoBack: state.canGoBack
});
}
}}
/>
</Fragment>
);
}
}
export default App;
The response above was perfect. I set the state true for canGoBack though; I was getting a null error, so:
constructor(props) {
super(props);
this.startingUrl = "https://app.vethorcardpag.com.br/GIF/login/0/";
this.state = {
canGoBack : true
}
this.handleBackButton = this.handleBackButton.bind(this);
}
Here is a simple solution using the magic of React's State.
Hope this helps.
import React, { useRef, useState } from 'react'
export default function Component () {
// This is used to save the reference of your webview, so you can control it
const webViewRef = useRef(null);
// This state saves whether your WebView can go back
const [webViewcanGoBack, setWebViewcanGoBack] = useState(false);
const goBack = () => {
// Getting the webview reference
const webView = webViewRef.current
if (webViewcanGoBack)
// Do stuff here if your webview can go back
else
// Do stuff here if your webview can't go back
}
return (
<WebView
source={{ uri: `Your URL` }}
ref={webViewRef}
javaScriptEnabled={true}
onLoadProgress={({ nativeEvent }) => {
// This function is called everytime your web view loads a page
// and here we change the state of can go back
setWebViewcanGoBack(nativeEvent.canGoBack)
}}
/>
)
}
Original answer
https://stackoverflow.com/a/74500469/7823800

React Native - How to detect home button is pressed or not?

I can know when the scene is on screen using following code:
this.subs = [
this.props.navigation.addListener('didFocus', this.loadOfflineData)
];
But , if I want to detect when scene is out of focus. I 'm wondering what should I do .
Basically I want to detect if the user press home button and then I want to perform some action.
Any suggestions.
You can use AppState to know when an app is put into the background.
AppState can tell you if the app is in the foreground or background, and notify you when the state changes.
Here is a simple example of using AppState:
import React, {Component} from 'react';
import {AppState, Text} from 'react-native';
class AppStateExample 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' ) {
console.log('App has come to the foreground!');
} else {
console.log('App has gone to the background!');
// start your background task here
}
this.setState({appState: nextAppState});
};
render() {
return <Text>Current state is: {this.state.appState}</Text>;
}
}
I've done this with android and java using the android lifecycle. This should work in React Native (see EDIT down below). the developer.android.com forum: this page
In short:
onCreate(): a user opens your app for the first time
onStart(): app restarts
onResume(): a user opens your app while it was active in the background
onPause(): app goes out of focus
onStop(): app is no longer visible
onDestroy(): activity will be destroyed
In your case you should use onPause()
So a little code snippet for in java:
#Override
public void onPause() {
super.onPause();
//do something
}
EDIT:
This answer tells you how to detect onPause and onResume in Reacte Native.
Code in that answer:
import React, {Component} from 'react'
import {AppState, Text} from 'react-native'
class AppStateExample 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') {
console.log('App has come to the foreground!')
}
this.setState({appState: nextAppState});
}
render() {
return (
<Text>Current state is: {this.state.appState}</Text>
);
}
}

React Native stop animation when leaving the app

I have an Animated.timing and I want to stop it when the user navigate to another app, is this possible?
you can use AppState like this:
import React, {Component} from "react"
import {AppState, View} from "react-native"
class YourClass extends Component {
state = {
appState: AppState.currentState
}
componentDidMount() {
//add a listener here
AppState.addEventListener("change", this._handleAppStateChange);
}
componentWillUnmount() {
// remember add this line
AppState.removeEventListener("change", this._handleAppStateChange);
}
_handleAppStateChange = (nextAppState) => {
if (this.state.appState.match(/inactive|background/) && nextAppState === "active"){
console.log("App has come to the foreground!")
}else{
//here you can call stop animation function
}
this.setState({appState: nextAppState});
}
render() {
return (
<Text>Current state is: {this.state.appState}</Text>
);
}
}

How to detect when keyboard is opened or closed in React Native

How to detect if user close the keyboard in react native, I want to call a function when user closed the keyboard.
and if you can answer to detect keyboard is open too it will be appreciated, thanks.
I'm on the react native latest version 0.56
Thank you guys for your answers. Here is the hooks version if someone is interested:
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => {
setKeyboardVisible(true); // or some other action
}
);
const keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => {
setKeyboardVisible(false); // or some other action
}
);
return () => {
keyboardDidHideListener.remove();
keyboardDidShowListener.remove();
};
}, []);
1. You can use Keyboard class from facebook.
Here is a sample code.
import React, { Component } from 'react';
import { Keyboard, TextInput } from 'react-native';
class Example extends Component {
componentWillMount () {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
}
componentWillUnmount () {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
_keyboardDidShow () {
alert('Keyboard Shown');
}
_keyboardDidHide () {
alert('Keyboard Hidden');
}
render() {
return (
<TextInput
onSubmitEditing={Keyboard.dismiss}
/>
);
}
}
###2. You can use some other npm dependency also, like react-native-keyboard-listener.
Import the component into the file you want to use it:
import KeyboardListener from 'react-native-keyboard-listener';
Use the component directly in your code. The component won't render anything
<View>
<KeyboardListener
onWillShow={() => { this.setState({ keyboardOpen: true }); }}
onWillHide={() => { this.setState({ keyboardOpen: false }); }}
/>
</View>
To install this dependency run below command.
npm install --save react-native-keyboard-listener
Choose any you feel more convenient.
I wrapped this up in a hook:
import { useState, useEffect } from 'react';
import { Keyboard } from 'react-native';
export const useKeyboardVisible = () => {
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => {
setKeyboardVisible(true);
},
);
const keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => {
setKeyboardVisible(false);
},
);
return () => {
keyboardDidHideListener.remove();
keyboardDidShowListener.remove();
};
}, []);
return isKeyboardVisible;
};
The hook returns a boolean flag that can be used to apply logic conditionally or run any other effect needed.
Improved version of #Khemraj 's answer (which worked great for me) with bound methods to the instance in order to be able to update the component's state from the listener and re-render.
import React, { Component } from 'react';
import { Keyboard, TextInput } from 'react-native';
class Example extends Component {
state = {
keyboardState: 'closed'
}
componentWillMount () {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
}
componentWillUnmount () {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
_keyboardDidShow = () => {
this.setState({
keyboardState: 'opened'
});
}
_keyboardDidHide = () => {
this.setState({
keyboardState: 'closed'
});
}
render() {
return (
<TextInput
onSubmitEditing={Keyboard.dismiss}
/>
);
}
}
I came across the usekeyboard hook found in #react-native-community/hooks
E.g.
import { useKeyboard } from '#react-native-community/hooks'
const keyboard = useKeyboard()
console.log('keyboard isKeyboardShow: ', keyboard.keyboardShown)
console.log('keyboard keyboardHeight: ', keyboard.keyboardHeight)
Source: https://github.com/react-native-community/hooks/blob/master/src/useKeyboard.ts
I used a small remix to the great answer from #halbano that may be useful to consider depending on your situation
export default function useKeyboardOffsetHeight(): number {
const [keyBoardOffsetHeight, setKeyboardOffsetHeight] = useState(0);
useEffect(() => {
const keyboardWillShowListener = Keyboard.addListener(
"keyboardWillShow",
(e) => {
setKeyboardOffsetHeight(e.endCoordinates.height);
}
);
const keyboardWillHideListener = Keyboard.addListener(
"keyboardWillHide",
() => {
setKeyboardOffsetHeight(0);
}
);
return () => {
keyboardWillHideListener.remove();
keyboardWillShowListener.remove();
};
}, []);
return keyBoardOffsetHeight;
}
keyBoardOffsetHeight vs isKeyboardVisible
I used integer keyBoardOffsetHeight instead of boolean isKeyboardVisible . Since it's often useful to move some of your other components based on the keyboard size. The offset is either 0 (hidden) or Y > 0 pixels (it is shown). Then I can use that in styling like so:
const keyBoardOffsetHeight = useKeyboardOffsetHeight();
...
<View
style={{
bottom: keyBoardOffsetHeight,
}}
>
keyboardWillShow vs keyboardDidShow
Secondly, I use keyboardWillShow events so that I can anticipate the keyboard appearing and fire my UI changes earlier.
The keyboardDidShow event fires once the keyboard has already appeared.
I also used keyboardWillHide instead of keyboardDidHide based on the same reasoning.
MobX version:
import { observable } from 'mobx'
import { EmitterSubscription, Keyboard } from 'react-native'
class KeyboardStore {
#observable isKeyboardVisible = false
keyboardSubs: EmitterSubscription[] = []
subKeyboard() {
this.keyboardSubs = [
Keyboard.addListener('keyboardDidShow', () => this.isKeyboardVisible = true),
Keyboard.addListener('keyboardDidHide', () => this.isKeyboardVisible = false),
]
}
unsubKeyboard() {
this.keyboardSubs.forEach(sub => sub.remove())
this.keyboardSubs = []
}
}
and inside top level App component
useEffect(() => {
store.subKeyboard()
return () => {
store.unsubKeyboard()
}
}, [])
and check anywhere in your app with store.isKeyboardVisible.
All the thing already avalable in react-native Keyboard class
import React, { Component } from 'react';
import { Keyboard, TextInput } from 'react-native';
class Example extends Component {
componentDidMount() {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
this.keyboardWillShowListener = Keyboard.addListener('keyboardWillShow', this._keyboardWillShow);
this.keyboardWillHideListener = Keyboard.addListener('keyboardWillHide', this._keyboardWillHide);
}
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
this.keyboardWillShowListener.remove();
this.keyboardWillHideListener.remove();
}
_keyboardWillShow() {
console.log('Keyboard Showning')
}
_keyboardWillHide() {
console.log('Keyboard Heding')
}
_keyboardDidShow() {
alert('Keyboard Shown');
}
_keyboardDidHide() {
alert('Keyboard Hidden');
}
render() {
return (
<TextInput
onSubmitEditing={Keyboard.dismiss}
/>
);
}
}

React Native setInterval doubles it self after restarting app

All the time setInterval acts as I am expecting except for one instance. If user leaves the app by clicking 'home button' and restarts app by clicking the icon (not by selecting from overview), the old setInterval haven't stopped. It starts over again, and there are now running 2 setIntervals. It goes on and on if I repeat this process and user can end up running hundreds of intervals at the same time.
How do I prevent this? I want to run only one instance of seInterval at the time.
Here's my code:
import React, { Component } from 'react';
import {
AsyncStorage,
Text,
View,
AppState,
} from 'react-native';
import timer from'react-native-timer';
class Code_temp extends Component {
redirect(routeName){
this.props.navigator.push({
name: routeName
});
}
constructor(props){
super(props);
this.state = {
appState: AppState.currentState,
};
}
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
this.setState({showMsg: true}, () => timer.setInterval(
this, 'hideMsg', () =>{
console.log( 1 );
}, 1000
));
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextAppState) => {
if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
}
this.setState({appState: nextAppState});
};
componentWillMount() {
}
render() {
return (
<View>
<Text>
Test
</Text>
</View>
);
}
}
export default Code_temp
That's because you are not clearing the setInterval when the component is unmounted, also remove 'this' from setinterval arguments
componentDidMount() {
timer.clearInterval('hideMsg');
AppState.addEventListener('change', this._handleAppStateChange);
this.setState({showMsg: true}, () => timer.setInterval(
'hideMsg', () =>{
console.log( 1 );
}, 1000
));
}
componentWillUnmount() {
timer.clearInterval('hideMsg');
};