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:

Mike Marcucio said...

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

Mike Marcucio said...
This comment has been removed by a blog administrator.
Simon MacDonald said...

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

Josh Hernandez said...

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

Simon MacDonald said...

@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?

karthick said...

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

Anonymous said...

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.

Simon MacDonald said...

@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.

Simon MacDonald said...

@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.

Unknown said...

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

Simon MacDonald said...

@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.

Layla said...

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!

Simon MacDonald said...

@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

Simon MacDonald said...

@Layla

Fixed checked in for 2.1.0.

Unknown said...

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?

Unknown said...

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.

Layla said...

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!!!

Unknown said...

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?

Simon MacDonald said...

@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.

Unknown said...

@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.

Simon MacDonald said...

@Velibor Mrdak

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

Unknown said...

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

Simon MacDonald said...

@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.

Akorede Ismael Olusola Enitan Jimoh said...

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.

Simon MacDonald said...

@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.

Akorede Ismael Olusola Enitan Jimoh said...

Thanks a lot Simon.

Worked.

I used the navigator.app.exitApp();

Thanks a lot once again for the quick reply.

Calebe said...

Is there some way to do that:

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

Simon MacDonald said...

@Joker

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

Calebe said...

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

Simon MacDonald said...

@Joker

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

Tio said...

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.

Simon MacDonald said...

@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?

Prakash said...

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

Simon MacDonald said...

@Prakash

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

Unknown said...

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

Simon MacDonald said...

@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.

Unknown said...

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