Event key "back" and Composer

I’m having trouble making the native button “back” on the android work correctly.

I tried everything and searched all over the subject on the internet and could not find a solution.

The app I’m creating has a button that rewinds scenes created with Composer.

But I would like to make this possible also through the Android back button.

Everything I have tried does not work correctly and the event propagates through all scenes because I am not able to cancel/remove the “Runtime: addEventListener” in each scene.

Here is part of my code to try to implement that function:

local function onKeyEvent( event ) local platformName = system.getInfo( "platformName" ) if ( platformName == "Android" ) then if ( event.keyName == "back" ) then composer.gotoScene( "scene1" ) composer.removeScene( "scene2" ) Runtime:removeEventListener( "key", onKeyEvent ) return true end end return false end -- -- Runtime:addEventListener( "key", onKeyEvent )

I appreciate if you can give me a solution that works and can be implemented within the pages I created with Composer.

Thank you,

Daniel

What do you mean by it does not work correctly? What is occurring that you do not want to happen or vice versa?

(1000th post, yay!)

Yes, we will need more details on what “doesn’t work”… does it even reach the function? Or is some condition in your code causing it to never even get there?

Brent

Don’t forget that there are two phases during pushing a button, a " up " and " down " phase.

So when you just touch the back button, the down phase happens and removes the listener, so when your finger leave the back button, the up phase happens but the Runtime key listener is already removed.

Actually, the button (back of Android) works, but in a strange way.

Every time I do a new build and do tests I notice the following:

I have five scenes inside the composer.

If I’m in scene 5, when I use the “back” button instead of going back to scene 4, it ends up going back to scene 3, or sometimes it leaves the application.

Or even sometimes it returns from the last scene to scene 1.

Or when I do some testing and I use the code in only 1 scene, I realize that the code does not close when the scene exits and spreads to the other scenes.

 

You have to disable the listener for the scenes you’re not in.  All scenes that have been activated are probably listening for this event.

Here a sample of the code.

I’m a newbie, someone can explain how to implement or to do it.

Thank you,

local composer = require( "composer" ) local scene = composer.newScene() -- ----------------------------------------------------------------------------------- -- Code outside of the scene event functions below will only be executed ONCE unless -- the scene is removed entirely (not recycled) via "composer.removeScene()" -- ----------------------------------------------------------------------------------- -- Here is the function local function onKeyEvent( event ) local platformName = system.getInfo( "platformName" ) if ( platformName == "Android" ) then if ( event.keyName == "back" ) then composer.gotoScene( "scene1" ) composer.removeScene( "scene2" ) Runtime:removeEventListener( "key", onKeyEvent ) return true end end return false end -- -- Runtime:addEventListener( "key", onKeyEvent ) -- -- -- ----------------------------------------------------------------------------------- -- Scene event functions -- ----------------------------------------------------------------------------------- -- create() function scene:create( event ) local sceneGroup = self.view -- Code here runs when the scene is first created but has not yet appeared on screen -- -- -- -- -- Here is my code end -- show() function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is still off screen (but is about to come on screen) elseif ( phase == "did" ) then -- Code here runs when the scene is entirely on screen end end -- hide() function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is on screen (but is about to go off screen) elseif ( phase == "did" ) then -- Code here runs immediately after the scene goes entirely off screen end end -- destroy() function scene:destroy( event ) local sceneGroup = self.view -- Code here runs prior to the removal of scene's view end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ----------------------------------------------------------------------------------- return scene

Hi @DanielApps,

A few things I can advise based on your code snippet:

  1. Don’t add/activate the Runtime key listener outside/above the Composer “scene event” blocks. It’s fine (and correct) that you include the listener function there, but I would only add/activate it within the “show > did” condition because, logically, that’s when it matters (IMHO the user shouldn’t be able to back out of the scene until the scene is fully visible).

  2. At the same time, REMOVE (deactivate) the Runtime listener in the “hide > will” condition area, because at that point, the user has clearly decided to leave the scene and they shouldn’t be able to re-leave the scene once they’re already leaving it (allowing that would cause unpredictable behavior).

  3. Add the phase detection of either “up” or “down” to the key event, and handle just one of them (not both).

I might also ask, what behavior is your goal for users who want to back-out of the app entirely using the back key? Will your app prevent it? Or will you make them back-out all the way to the first home scene before letting them back out of the app? Just something to consider…

Brent

So far, I am very grateful for the help of everyone.

I will try all possibilities until I get something satisfactory.

Regarding your question, Brent, the app already has a return button for the scenes (this app is just for presenting a portfolio), but I’d like to add the native Android button “back”.

Ideally, the user would return to the first scene and then might suggest a warning like, “Do you really want to quit the application?” Or something like that.

But I do not know if I can do this quickly and efficiently, as I said above I’m just an illustrator and beginner in programming.

Thank you!

@DanielApps

Try keeping the back key event stuff out of the composer scenes.

Instead try doing something like this in your main.lua file:

--  main.lua -- require the composer library local composer = require "composer" composer.gotoScene( "scene1" ) -- Key listener local platformName = system.getInfo( "platformName" ) local function onKeyEvent( event ) if ( platformName == "Android" ) then if event.phase == "down" then if event.keyName == "back" then local currScene = composer.getSceneName( "current" ) local prevScene = composer.getSceneName( "previous" ) if currScene == "firstScene" then -- present an alert asking if the user wants to quit the app. elseif prevScene then composer.gotoScene( prevScene ) end return true end end end end Runtime:addEventListener( "key", onKeyEvent )

@DanielApps,

Got this working yet?

I’m still encountering some problems.

The solution given by @ojnab works, only that a problem occurs, the scene only goes back and forth.

For example, I have five scenes, if I am in scene 5 the “back” button only returns to scene 4 and if I press again, it returns to scene 5.

The “back” button does not return scene by scene until the first scene is reached.

I have a bunch of composer.* examples here: https://github.com/roaminggamer/CoronaGeek/raw/master/Hangouts/composer_scene_manager.zip

I just added a new example doing basically what you want to do with a module. 

Download from the above link and see example 15_android_back_nav.

When you poke through the scene files (found in ifc/ folder), look for the marker:  BACK_HANDLER

See the module android.lua for how I did this.

To use the module, do the following (in your scene files)

Normal Scene File

  1. Somewhere towards top of file:

    local android = require “android” local backHandler

2a. In create() method of your scene, If you intend to exit app from this scene:

 if( not backHandler ) then backHandler = android.new( { debugEn = true, title = "Quit Game?", msg = "This will exit the game.\n\nAre you sure you want to quit?" } ) end

2b. In create() method of your scene, If you intend to return to another scene:

-- This assumes onBack is a function that calls the right composer functions to go back to another -- scene from this one -- -- AND... that onBack has already been defined by this point -- if( not backHandler ) then backHandler = android.new( { debugEn = true, onBack = onBack } ) end -- Tip: debugEn true enables the escape key so you can test this in the simulator
  1. In the ‘did’ phase of show() method for your scene.

    backHandler:activate() – BACK_HANDLER

  2. In the ‘will’ phase of hide() method for your scene.

    backHandler:deactivate() – BACK_HANDLER

Overlays

Overlays are a little trickier because they don’t trigger the will or did phases of the scene that is being overlayed.  So you have to know this and act accordingly.

This example shows how to handle this, and I won’t show you here.  Just look for the markers:  BACK_HANDLER

Warning

My scene files are a LITTLE different from the ones Corona shows.  I have split the show() and hide() methods into:

  • willShow()
  • didShow()
  • willHide()
  • didHide()

I do this because I like my code kept clearly separate.

Examine my samples carefully (see code at very bottom) and you’ll see how I did this.

In action…

https://www.youtube.com/watch?v=wAou9VUVqs8&feature=youtu.be

roaminggamer

Unfortunately, this still does not work.

When I am in scene 3 and I use the “back” button, it only shows the warning screen (Exit? Are you sure? Yes - No), instead of going back to scene 2.

When I am in scene 1 and press the “back” button, the warning screen (Quit Game? … Yes - No) appears.

If I select “Yes” the application is not closed.

Double and triple check your usage.  

Not exiting app while in simulator or on phone?

If in simulator, no prob.  It is calling:

native.requestExit()

That should exit the app while on Android though

Not exiting when on the phone, I do not know how to test this on the simulator.

I tested your example (15-android-back-nav) and realized that on the last screen it also does not exit the application.

On windows simulator you can use Hardware > Back menu option (or Alt + Left arrow shortcut)

Thank you for your help, Benzeliden.

@Daniel,

0. Please re-download the example.   I updated it further and fully tested it on a device.

1.  You must have missed it, but I stated that debugEn = true in the new() call will allow you to use the ‘escape’ key to test back.  Still you can use the suggestion Benzeliden gave too.  I just like simple single keystrokes when testing.

  1. I found and fixed an issue/bug in the module.   This caused the ‘exit’ call to fail on deviecs.  I had the code out of order:

Was like this:

 local alert alert = native.showAlert( params.title, params.msg, { "NO", "YES" }, onComplete ) local function onComplete( event ) if "clicked" == event.action then local i = event.index if 1 == i then native.cancelAlert( alert ) alert = nil elseif 2 == i then if( params.onExit ) then params.onExit() end native.requestExit() end end end -- ISSUE? onComplete wasn't defined before creation of alert

Fix:

 local alert local function onComplete( event ) if "clicked" == event.action then local i = event.index if 1 == i then native.cancelAlert( alert ) alert = nil elseif 2 == i then if( params.onExit ) then params.onExit() end native.requestExit() end end end alert = native.showAlert( params.title, params.msg, { "NO", "YES" }, onComplete )
  1. No matter what you do (short of calling os.exit()), you cannot test exiting the app in the simulator.

  2. This update includes the following changes:

  • scenes changed to: 1,2,3,4,5, options
  • scene 1 leads to 2 and 3
  • scene 2 leads to 4 or back to 1
  • scene 3 leads to 5 or back to 1
  • scene 4 leads back to 2
  • scene 5 leads back to 3

This will let you fully test the idea of back button to go back to last scene.