Android Back Button on a Progressive Web Application closes de App

rmpestano picture rmpestano · Apr 10, 2017 · Viewed 14.6k times · Source

Can a "pure" HTML5/Javascript (progressive) web application intercept the mobile device back button in order to avoid the App to exit?

This question is similar to this one but I want to know if it is possible to achieve such behavior without depending on PhoneGap/Ionic or Cordova.

Answer

alecdwm picture alecdwm · Apr 8, 2018

While the android back button cannot be directly hooked into from within a progressive web app context, there exists a history api which we can use to achieve your desired result.

First up, when there's no browser history for the page that the user is on, pressing the back button immediately closes the app.
We can prevent this by adding a previous history state when the app is first opens:

window.addEventListener('load', function() {
  window.history.pushState({}, '')
})

The documentation for this function can be found on mdn:

pushState() takes three parameters: a state object, a title (which is currently ignored), and (optionally) a URL[...] if it isn't specified, it's set to the document's current URL.

So now the user has to press the back button twice. One press brings us back to the original history state, the next press closes the app.


Part two is we hook into the window's popstate event which is fired whenever the browser navigates backwards or forwards in history via a user action (so not when we call history.pushState).

A popstate event is dispatched to the window each time the active history entry changes between two history entries for the same document.

So now we have:

window.addEventListener('load', function() {
  window.history.pushState({}, '')
})

window.addEventListener('popstate', function() {
  window.history.pushState({}, '')
})

When the page is loaded, we immediately create a new history entry, and each time the user pressed 'back' to go to the first entry, we add the new entry back again!


Of course this solution is only so simple for single-page apps with no routing. It will have to be adapted for applications that already use the history api to keep the current url in sync with where the user navigates.

To do this, we will add an identifier to the history's state object. This will allow us to take advantage of the following aspect of the popstate event:

If the activated history entry was created by a call to history.pushState(), [...] the popstate event's state property contains a copy of the history entry's state object.

So now during our popstate handler we can distinguish between the history entry we are using to prevent the back-button-closes-app behaviour versus history entries used for routing within the app, and only re-push our preventative history entry when it specifically has been popped:

window.addEventListener('load', function() {
  window.history.pushState({ noBackExitsApp: true }, '')
})

window.addEventListener('popstate', function(event) {
  if (event.state && event.state.noBackExitsApp) {
    window.history.pushState({ noBackExitsApp: true }, '')
  }
})

The final observed behaviour is that when the back button is pressed, we either go back in the history of our progressive web app's router, or we remain on the first page seen when the app was opened.