React Native: Double back press to Exit App
import React, {Component} from 'react';
import {BackHandler, View, Dimensions, Animated, TouchableOpacity, Text} from 'react-native';
let {width, height} = Dimensions.get('window');
export default class App extends Component<Props> {
state = {
backClickCount: 0
};
constructor(props) {
super(props);
this.springValue = new Animated.Value(100) ;
}
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton.bind(this));
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton.bind(this));
}
_spring() {
this.setState({backClickCount: 1}, () => {
Animated.sequence([
Animated.spring(
this.springValue,
{
toValue: -.15 * height,
friction: 5,
duration: 300,
useNativeDriver: true,
}
),
Animated.timing(
this.springValue,
{
toValue: 100,
duration: 300,
useNativeDriver: true,
}
),
]).start(() => {
this.setState({backClickCount: 0});
});
});
}
handleBackButton = () => {
this.state.backClickCount == 1 ? BackHandler.exitApp() : this._spring();
return true;
};
render() {
return (
<View style={styles.container}>
<Text>
container box
</Text>
<Animated.View style={[styles.animatedView, {transform: [{translateY: this.springValue}]}]}>
<Text style={styles.exitTitleText}>press back again to exit the app</Text>
<TouchableOpacity
activeOpacity={0.9}
onPress={() => BackHandler.exitApp()}
>
<Text style={styles.exitText}>Exit</Text>
</TouchableOpacity>
</Animated.View>
</View>
);
}
}
const styles = {
container: {
flex: 1,
justifyContent: "center",
alignItems: "center"
},
animatedView: {
width,
backgroundColor: "#0a5386",
elevation: 2,
position: "absolute",
bottom: 0,
padding: 10,
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
},
exitTitleText: {
textAlign: "center",
color: "#ffffff",
marginRight: 10,
},
exitText: {
color: "#e5933a",
paddingHorizontal: 10,
paddingVertical: 3
}
};
Run in snack.expo: https://snack.expo.io/HyhD657d7
I've solved it this way as separated functional Component. This way you don't need to recode it for each app, only include the component in your new App and you've done!
import * as React from 'react';
import {useEffect, useState} from 'react';
import {Platform, BackHandler, ToastAndroid} from 'react-native';
export const ExecuteOnlyOnAndroid = (props) => {
const {message} = props;
const [exitApp, setExitApp] = useState(0);
const backAction = () => {
setTimeout(() => {
setExitApp(0);
}, 2000); // 2 seconds to tap second-time
if (exitApp === 0) {
setExitApp(exitApp + 1);
ToastAndroid.show(message, ToastAndroid.SHORT);
} else if (exitApp === 1) {
BackHandler.exitApp();
}
return true;
};
useEffect(() => {
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
backAction,
);
return () => backHandler.remove();
});
return <></>;
};
export default function DoubleTapToClose(props) {
const {message = 'tap back again to exit the App'} = props;
return Platform.OS !== 'ios' ? (
<ExecuteOnlyOnAndroid message={message} />
) : (
<></>
);
}
=> Only thing you need is to include this component in your App. <=
Because IOS don't have a back-button, IOS don't need this functionality. The above Component automatically detect if Device is Android or not.
By default the Message shown in Toast is predefined in English, but you can set your own one if you add an property named message
to your DoubleTapToClose-Component.
...
import DoubleTapToClose from '../lib/android_doubleTapToClose';
...
return(
<>
<DoubleTapToClose />
...other Stuff goes here
</>
Depending on your Nav-Structure, you have to check if you are at an initialScreen of your App or not. In my case, I have an Drawer with multiples StackNavigatiors inside. So I check if the current Screen is an initial-Screen (index:0), or not. If it is, I set an Hook-Variable which will only be used for those initial-Screens.
Looks like this:
const isCurrentScreenInitialOne = (state) => {
const route = state.routes[state.index];
if (route.state) {
// Dive into nested navigators
return isCurrentScreenInitialOne(route.state);
}
return state.index === 0;
};
...
...
export default function App() {
...
const [isInitialScreen, setIsInitialScreen] = useState(true);
{isInitialScreen && (<DoubleTapToClose message="Tap again to exit app" />)}
...
...
<NavigationContainer
...
onStateChange={(state) => {
setIsInitialScreen(isCurrentScreenInitialOne(state));
}}>
If that description helps you out, don't miss to vote.
Sorry if I'm late to the party, but I had a similar requirement and solved it by creating my own custom hook!
let currentCount = 0;
export const useDoubleBackPressExit = (exitHandler: () => void) => {
if (Platform.OS === "ios") return;
const subscription = BackHandler.addEventListener("hardwareBackPress", () => {
if (currentCount === 1) {
exitHandler();
subscription.remove();
return true;
}
backPressHandler();
return true;
});
};
const backPressHandler = () => {
if (currentCount < 1) {
currentCount += 1;
WToast.show({
data: "Press again to close!",
duration: WToast.duration.SHORT,
});
}
setTimeout(() => {
currentCount = 0;
}, 2000);
};
Now I can use it anywhere I want by simply doing:
useDoubleBackPressExit(() => {
// user has pressed "back" twice. Do whatever you want!
});