Wednesday, May 4, 2011

Overriding the Back Button in PhoneGap Android

Frequently PhoneGap applications consist of multiple screens represented by divs. Since Android users are conditioned to push the back button to return to the previous screen this causes some confusion as the default behaviour is to close the app. Luckily PhoneGap provides developers a way to over ride the default back button behaviour so they may provide their own back behaviour.

In release 0.9.4 the developer would have to wait for the deviceready event and call:

navigator.device.overrideBackButton();
document.addEventListener("backKeyDown", backPressed, false);

In order to setup and event handler for the back key and:

navigator.device.resetBackButton();

to revert to the default behaviour.

As of PhoneGap 0.9.5 we've made overriding the back button behaviour easier. You still have to wait for the deviceready event but the calls have been simplified to:

document.addEventListener("backbutton", onBackKey, false);

to register your event handler for the back key and:

document.removeEventListener("backbutton", onBackKey, false);

to revert to the default Android back button behaviour.

The following listing shows a full, yet contrived, example:

37 comments:

  1. How about the menu and search buttons? can they be accesses the same ways?

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. Hey Mike,

    Yes, yes they can. In fact I just updated the documentation last night.

    http://docs.phonegap.com/phonegap_events_events.md.html#Events

    ReplyDelete
  4. Hi Simon,

    Did something change with phonegap 1.0? I spent all day yesterday trying to get the listener to fire on my app. I was finally able to make it work by switching back to phonegap 0.9.5, but that breaks the rest of my code. I'm using the code from the link you provided, and a simple print to logs, and it's just ignoring my listener. Do you have any ideas?

    Thank you,
    Josh

    ReplyDelete
  5. @Josh

    No nothing jumps to mind as to why the back button handler wouldn't work for you. Can you update how things are going over on google groups?

    ReplyDelete
  6. How to access webservices in phonegap.i am using asp.net webservices

    ReplyDelete
  7. I've downloaded PhoneGap 1.0 and I've implemented the code here, and it ignores the handler and the back key continues to exit the app.

    ReplyDelete
  8. @karthick Your question is better answered over on the PhoneGap Google Group. The short answer is to make a XHR request to your ASP webservice to retrieve the data as XML or JSON.

    ReplyDelete
  9. @Unknown did you make sure you updated the link to the phonegap.js file? In my example I use 0.9.5 but you have downloaded 1.0.0. Also, it still leaves the app when you are at the first screen as that is desired behaviour.

    ReplyDelete
  10. Hi Simon.

    It is good to find helpful information on your blog. I need to handle the back button for certain pages in my app.

    Because the button handling control using document.addEventListener('backbutton', ...) triggers on a button-down action, holding the back button down can cause a cascade of back button actions. e.g. from page 2 to page 1 to exiting the app.

    I think "back button up" (NOT back button down) should trigger the PhoneGap back button event.

    Is the success callback (e.g. onBackKey) needed for the document.removeEventListener("backbutton", onBackKey, false) API call? Logically, why would onBackKey be called when I am disabling backbutton handling?

    Thanks,
    David

    ReplyDelete
  11. @David Wong

    You have a good point about the back button up. Let me look into that.

    Yes, you need the success callback in the removeEventListener in order for the framework to know what success call back to be removed as you can register more than one.

    ReplyDelete
  12. And what with alerts and prompts? When I do a navigator.notification.alert('hello!') and I try to remove it by pressing the backbutton, this does not work in phonegap 1.9.0...

    Is there a solution for this? As it is standard android behaviour and I would like to mimick it...

    Thanks in advance!

    ReplyDelete
  13. @Layla

    That is a bug. Thanks for finding it. I've raised an issue to track it and it should be fixed in 2.1.0.

    https://issues.apache.org/jira/browse/CB-1217

    ReplyDelete
  14. Hi Simon,

    when an input field is focused in my Phonegap (Cordova 2.0.0) Application on Android the softkeyboard shows up. When pressing the backbutton it closes the softkeyboard and the input field still stays focues. Whenn pressing again the backButton the app is closing INSTEAD of going back in history. This only happens when an input field is focused.

    How can I change this?

    ReplyDelete
  15. I found a solution to my question: in Android 4.0.3 (ICS) you do not have to override backButton with phongap API to stop closing/crashing the app after having focused an input text-field and then pressing the back button. Normally this closes the app, because webkit is creating a box-highlight with an additional outline that connot be changed with css.

    When you focus the input the softkeyboard comes up. When you press the first time the softkeyboard disappears. When you click again to go back in navigation-history, the app is closing instead of jumping to the page visited before. This is because the highlighting jumps out of the navigation-structure.

    FIX:
    just add

    input {
    -webkit-user-modify: read-write-plaintext-only
    }

    This interrupts webkit doing a tap-highlight and you still stay in the app and can go back in History with your (not overriden) backButton.

    Enjoy.

    ReplyDelete
  16. Hi Simon, big thanks! (sorry for the late answer, I was on holiday) Btw, I saw your twitter question whether you should keep your comments open... and yes, definitely!!!

    ReplyDelete
  17. Hi Simon,

    I`m trying to accomplish more than one addEventListener, but something is wrong?

    For example if we have 3 divs on page and first one is displayed on the beginning and other two hidden.

    div id="d1"
    a onClick="
    document.addEventListener("backbutton", show_div1, false);
    $('#d1').hide();
    $('#d2').show();
    "
    a
    div

    div id="d2" - initially hidden
    div id="d3" - initially hidden


    It shows div 2, hides div 1 and sets listener for back button to show_div1(), and everything works OK. On back key pressed it alerts "I should show #div1", as it should (//$('#d1').show(); is comented)

    show_div1(){
    //$('#d1').show(); $('#d2').hide();
    alert ('I should show #div1');
    }

    But now comes the problem

    div id="d2"
    a onClick="
    document.removeEventListener("backbutton", show_div1, false);
    document.addEventListener("backbutton", show_div2, false);
    $('#d2').hide();
    $('#d3').show();
    "
    a
    div

    It immediately fires "I should show #div2" even back button is not pressed! addEventListener like started main function show_div2() and not just set listener on back button to that function.

    show_div2(){
    //$('#d2').show(); $('#d3').hide();
    alert ('I should show #div2');
    }


    What could be the possible reason for this happening?

    ReplyDelete
  18. @Velibor Mrdak

    Hmmm...what version of PhoneGap are you using? If the add/remove causes the event to fire immediately we could have a bug.

    ReplyDelete
  19. @Simon

    Ver. 2.0.0 & 2.1.0 - same thing

    I can post some sample code, but maybe there is not need for it, because you can create sample fns

    test(){alert('ok');}
    test1(){alert('ok1');}

    and attach in onDeviceReady() {
    document.addEventListener("backbutton", test, false);
    } it works OK, but if you try later

    document.removeEventListener("backbutton", test, false);
    and
    document.addEventListener("backbutton", test1, false);

    it immediately fires "alert ok1".

    To sum up If addEventListener more than once it is not working as expected (just to attach fn to listener) it launches test1() fn

    I think that I was clear with explanation.

    I found workaround with adding some extra code to fn and attaching listener only once, but it would be nice (if already have fns that exists in code) to attach as many listeners as I want depending on current situation.

    ReplyDelete
  20. @Velibor Mrdak

    I am sorry, I just can't reproduce this.

    ReplyDelete
  21. I did not understand it? Reproduce what? Code? Should I upload index.html file?

    ReplyDelete
  22. @Velibor Mrdak

    Yeah, if you could post your index.html file somewhere I will test it. I can't seem to reproduce the problem in my own code.

    ReplyDelete
  23. Hi Simon,

    I am using cordova 2.2 and after overwriting the back button I want user to be able to still close the app if back button is pressed in the home page?

    Any suggestions?

    I used this: navigator.device.resetBackButton();

    But it had no visible effect.

    Thanks in advance.

    ReplyDelete
  24. @Akorede Ismael Olusola Enitan Jimoh

    Well if you are over-riding the back button you can detect you are on the home page and call navigator.app.exitApp() to close your app. Alternatively you can remove the back button event listener when you are on the main page and then the default behaviour will be returned.

    ReplyDelete
  25. Thanks a lot Simon.

    Worked.

    I used the navigator.app.exitApp();

    Thanks a lot once again for the quick reply.

    ReplyDelete
  26. Is there some way to do that:

    If user is on #home, EXIT, if is not, use navigator.app.backHistory()

    ReplyDelete
  27. @Joker

    Sure you can do that. You just have to keep track of the url you are currently on.

    ReplyDelete
  28. I have tried it with location, but it dont works with JquerMobile. It uses just ont file, for pages it uses hashs...

    ReplyDelete
  29. @Joker

    Yes, and isn't the hash part of the url?

    ReplyDelete
  30. Simon I was using the cordova 2.1.0 and my backbutton override was working nice. After I upgrade to the 2.2.0 when I hit the backbutton my app closes like there is no override. When I click on the back button my callback function is not being called. Here is how I'm doing:

    if (Ext.os.is('Android')) {
    document.addEventListener("backbutton", Ext.bind(onBackKeyDown, this), false);
    function onBackKeyDown(e) {
    alert('Back Button');
    e.preventDefault();
    if ( Ext.Viewport.getActiveItem().xtype == 'panelmenu' ) {
    navigator.app.exitApp();
    } else {
    this.getApplication().getHistory().add(Ext.create('Ext.app.Action', {
    url: 'panelmenu'
    }));
    }}}

    This code is inside the launch() function on app.js. I'm using sencha touch 2.

    ReplyDelete
  31. @Tio

    What do you see in "adb logcat" when you click the back button? Also, you are closing the app when you detect you are on "panelmenu". Are you sure you're not running into that case?

    ReplyDelete
  32. Does this work on devices running on Honeycomb and higher with on screen navigation button? Some how it doesnt work for me.

    ReplyDelete
  33. @Prakash

    Works fine for me. Remember to call the addEventListener after you get the deviceready event.

    ReplyDelete
  34. Hi

    My app is a javascript app and uses Sencha Touch 2.1 and Phonegap 2.5.

    I have a problem with the device back button on Android: My app uses the device back button to navigate to the previous view. Now, if I press the back button twice really quickly, then my app stops working. I suspect that what happens is that I get a second onBackButton() event callback before the previous one is fully served, i.e. before my app is ready to handle another back button event.

    I am basically using similar code as the example code above to catch the back button events in my app.

    I wonder if anyone can help me a little...?

    Cheers
    Tommi

    ReplyDelete
  35. @Tommi Joutsiniemi

    I haven't seen that type of behaviour but you could try guarding the code with a boolean. Set it to true when your back code is executing then false when you are done. Should ignore the double click.

    ReplyDelete
  36. Turned out this is a... feature in Sencha Touch.

    The pop() method of NavigationView class is asynchronous, so a simple boolean guard is not enough - I need a way to know if the previous pop() operation has finished, i.e. if another pop() is allowed to happen.

    Anyway, I solved this with a "complex guard", basically I check if the previous pop() has actually been done before I allow another one.

    Thank You

    ReplyDelete