├── .gitignore ├── .npmignore ├── README.md ├── assets ├── double-back.gif └── happy.gif ├── helpers ├── getCurrentRoute.js └── noop.js ├── index.js ├── package.json └── src ├── ExitOnDoubleBack.component.android.js └── ExitOnDoubleBack.component.ios.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | assets 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # exit-on-double-back 2 | 3 | A react-native module to exit an app when the back button is pressed twice on android device. 4 | 5 | Note: This module only works with [react-navigation](https://reactnavigation.org/)(as of now) and your navigator should be [integrated to redux store](https://reactnavigation.org/docs/guides/redux). 6 | 7 | ## Why do I need this module? 8 | 9 | If you need to perform some action on your app when the back button is pressed twice on the landing screen of the application, this module is for you. 10 | 11 | ## Props 12 | 13 | This module accepts the following props: 14 | 15 | | Prop |Explanation| Default Value |Type | 16 | |----------|:------------------|:--------------|:--------------| 17 | |`toastMessage`|Message to be displayed on the toast when the back button is pressed on the landing screen.|'Press back again to exit the app'|string| 18 | |`doubleBackInterval`|Interval(in ms) in which JS will wait for second back press|3000|number| 19 | |`exitableRoutes`|Route names where toast message will be shown on first back press|['Landing']|Array|| 20 | |`onDoubleBack`|Function to be called on double back press in the passed interval. If no function is passed, the app will exit.|`BackHandler.exitApp`|Function|| 21 | |`backHandler`|Function to be called on normal back presses of the application. |noop|Function| 22 | |`nav`|Your router state from the redux store(will be used to get current route name)|{}|Object| 23 | 24 | ## Demo 25 | 26 |

27 | 28 |

29 | 30 | ## Usage 31 | 32 | After integration react-navigation with redux store, your App would look something like this: 33 | ```js 34 | class App extends React.Component { 35 | render() { 36 | const {dispatch, nav} = this.props; 37 | return ( 38 | 39 | ); 40 | } 41 | } 42 | const mapStateToProps = (state) => ({ 43 | nav: state.nav 44 | }); 45 | ``` 46 | 47 | To use this component, just wrap this component around `ExitOnDoubleBack` and pass the required props. 48 | 49 | ```js 50 | import ExitOnDoubleBack from 'exit-on-double-back'; 51 | 52 | class App extends React.Component { 53 | render() { 54 | const {dispatch, nav, goBack} = this.props; 55 | return ( 56 | 57 | 58 | 59 | ); 60 | } 61 | } 62 | const mapStateToProps = (state) => ({ 63 | nav: state.nav 64 | }); 65 | const mapDispatchToProps = (dispatch) => ({ 66 | goBack: () => this.props.dispatch(NavigationActions.back()) 67 | }) 68 | ``` 69 | If you wish to override the default behavior of exiting the app or add your own message/interval, you can pass additional props. 70 | 71 | ```js 72 | 73 | 74 | 75 | ``` 76 | 77 | That's all, your app now supports double back press to exit feature. 78 | 79 |

80 | 81 |

82 | 83 | 84 | 85 | >Note: I have used JS `setTimeout` instead of [Timers](https://facebook.github.io/react-native/docs/timers.html). The Timer module is a react mixin and react team is [discouraging](https://github.com/brigand/react-mixin) the use of mixins in favour of higher order components. I am handling clearing the timeout as well whenever the action is done or the component unmounts. 86 | 87 | 88 | Don’t forget to hit star if you like my work :) 89 | -------------------------------------------------------------------------------- /assets/double-back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgabs/exit-on-double-back/2d5b7e9a7d572c788e73d5bee76452aa79b71b53/assets/double-back.gif -------------------------------------------------------------------------------- /assets/happy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgabs/exit-on-double-back/2d5b7e9a7d572c788e73d5bee76452aa79b71b53/assets/happy.gif -------------------------------------------------------------------------------- /helpers/getCurrentRoute.js: -------------------------------------------------------------------------------- 1 | function getCurrentRouteName (navigationState) { 2 | if (!navigationState) { 3 | return null; 4 | } 5 | const route = navigationState.routes[navigationState.index]; 6 | if (route.routes) { 7 | return getCurrentRouteName(route); 8 | } 9 | return route.routeName; 10 | } 11 | 12 | module.exports = getCurrentRouteName; 13 | -------------------------------------------------------------------------------- /helpers/noop.js: -------------------------------------------------------------------------------- 1 | const noop = () => {}; 2 | export default noop; 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import ExitOnDoubleBack from './src/ExitOnDoubleBack.component'; 2 | 3 | export default ExitOnDoubleBack; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exit-on-double-back", 3 | "version": "1.0.1", 4 | "description": "Exits app when back button is pressed twice in the passed interval on the index route of react-navigation", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/rgabs/exit-on-double-back.git" 10 | }, 11 | "keywords": [ 12 | "react-native", 13 | "react-navigation", 14 | "BackAndroid", 15 | "Toast", 16 | "Exit", 17 | "Back", 18 | "BackHandler", 19 | "Logout", 20 | "DoubleBack" 21 | ], 22 | "author": "Rahul Gaba", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/rgabs/exit-on-double-back/issues" 26 | }, 27 | "homepage": "https://github.com/rgabs/exit-on-double-back#readme", 28 | "dependencies": {}, 29 | "devDependencies": {} 30 | } 31 | -------------------------------------------------------------------------------- /src/ExitOnDoubleBack.component.android.js: -------------------------------------------------------------------------------- 1 | /* ExitOnDoubleBack: Exits app when back button is pressed twice in the passed interval*/ 2 | 3 | import getCurrentRouteName from '../helpers/getCurrentRoute'; 4 | import PropTypes from 'prop-types'; 5 | import React, {Component} from 'react'; 6 | import {BackHandler, ToastAndroid} from 'react-native'; 7 | import noop from '../helpers/noop'; 8 | 9 | class ExitOnDoubleBack extends Component { 10 | componentWillMount () { 11 | BackHandler.addEventListener('hardwareBackPress', this._handleBackPress); 12 | } 13 | timer = { 14 | ref: null, 15 | isTimerRunning: false 16 | } 17 | _handleExit = () => { 18 | if (!this.timer.isTimerRunning) { 19 | this.timer.isTimerRunning = true; 20 | const backInterval = this.props.doubleBackInterval; 21 | clearTimeout(this.timer.ref); 22 | this.timer.ref = setTimeout(() => this.timer.isTimerRunning = false, backInterval); 23 | ToastAndroid.show(this.props.toastMessage, ToastAndroid.SHORT); 24 | return true; // don't do anything 25 | } 26 | return this.props.onDoubleBack && this.props.onDoubleBack(); 27 | } 28 | 29 | _handleBackPress = () => { 30 | const {nav, backHandler} = this.props; 31 | const currentRoute = getCurrentRouteName(nav); 32 | if (this.props.exitableRoutes.includes(currentRoute)) { // exit the app from landing page 33 | return this._handleExit(); 34 | } else { // in all the other cases, navigate back 35 | backHandler(); 36 | return true; 37 | } 38 | } 39 | componentWillUnmount () { 40 | BackHandler.removeEventListener('hardwareBackPress', this._handleBackPress); 41 | clearTimeout(this.timer.ref); 42 | this.timer = { 43 | ref: null, 44 | isTimerRunning: false 45 | }; 46 | } 47 | 48 | render () { 49 | return this.props.children; 50 | } 51 | } 52 | ExitOnDoubleBack.defaultProps = { 53 | toastMessage: 'Press back again to exit the app', 54 | doubleBackInterval: 3000, 55 | exitableRoutes: ['Landing'], 56 | onDoubleBack: BackHandler.exitApp, 57 | backHandler: noop, 58 | nav: {} 59 | }; 60 | ExitOnDoubleBack.propTypes = { 61 | toastMessage: PropTypes.string, 62 | doubleBackInterval: PropTypes.number, 63 | exitableRoutes: PropTypes.array, 64 | children: PropTypes.node, 65 | onDoubleBack: PropTypes.func, 66 | backHandler: PropTypes.func, 67 | nav: PropTypes.object, 68 | }; 69 | export default ExitOnDoubleBack; 70 | -------------------------------------------------------------------------------- /src/ExitOnDoubleBack.component.ios.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const EmptyWrapperComponent = ({children}) => children; 5 | 6 | EmptyWrapperComponent.propTypes = { 7 | children: PropTypes.element.isRequired //accepts single child 8 | }; 9 | 10 | export default EmptyWrapperComponent; 11 | --------------------------------------------------------------------------------