├── .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 |
--------------------------------------------------------------------------------