composer bug: function show (did) running twice

I was thinking that in the will phase, set a flag using setVariable() that you’re in the will phase. When it’s the show phase, set the flag to be “did”.

function scene:show( event )       scene.setVariable( "phase", event.phase)       if event.phase == "will" then             ...       else             ...       end end

Then your module can know what phase you’re in.

Rob

Do you know a way to intercept all “show” functions?

For example, today I intercept all .gotoScene() functions by doing:

local composer = require("composer") local gotoScene = composer.gotoScene composer.gotoScene = function(a,b) print("I am intercepting gotoScene. Doing something here...") gotoScene(a,b) end

Since the ‘show’ is a listener function specific for each scene, I am not finding a simple way to do it…

it won’t be easy, because what you’d need to do is monkey-patch dispatchEvent() (as you did with gotoScene).

the problem is, you need to do that on each *scene*, not on composer.

and the problem is compounded because your scene may not “exist” prior to your call to gotoScene() unless you explicity pre-load it…

basically it would look something like this  (consider this pseudocode, just to illustrate concepts):

-- in scene1, trying to goto scene2 local otherScene = composer.loadScene("scene2", ...any other needed params -- now you have the preloaded scene, you can monkey with it -- (you could write a helper function to apply this monkeying, i'll just do it inline) otherScene.\_dispatchEvent = otherScene.dispatchEvent -- save original otherScene.dispatchEvent = function(self, event) print("I intercepted this event:", event.name) if (some condition that says i still want original behavior) then self:\_dispatchEvent(event) -- call original else -- do something OTHER than default behavior end end composer:gotoScene("scene2", ...

Thanks for the help @davebollinger. Having to add a code on each scene is something that I was trying to avoid.

I was able to find a simple fix for the bug with a single code/require.

This is my solution:

rb-composer.lua

local composer = require "composer" composer.\_gotoScene = composer.gotoScene composer.gotoScene = function(sceneName, options) local currScene = composer.getScene( composer.getSceneName( "current" ) ) if currScene then if currScene.\_currEventName == "show" and currScene.\_currEventPhase == "will" then --print("current Scene is on 'SHOW - WILL'. Let's not execute the gotoScene now due to composer bug. Let's try again in a few so the scene can be on 'SHOW - DID'") return timer.performWithDelay(10, function() composer.gotoScene(sceneName, options) end) end end composer.\_gotoScene(sceneName, options) end composer.\_newScene = composer.newScene composer.newScene = function() local scene = composer.\_newScene() function scene.trap\_event(event) scene.\_currEventName = event.name scene.\_currEventPhase = event.phase end scene:addEventListener( "create", scene.trap\_event ) scene:addEventListener( "show", scene.trap\_event ) scene:addEventListener( "hide", scene.trap\_event ) scene:addEventListener( "destroy", scene.trap\_event ) return scene end

and on my main.lua I just require that file:

require "rb-composer"

That file (rb-composer) basically adds to every scene its last event info and also overwrites the composer.gotoScene forcing it to check if the current scene is not on a show/will phase. If it is, it delays the gotoScene until it is not.

That solution allows me to avoid the bug and not require me to make any other change on my project.

Thanks for all the help guys.

UPDATE: Simplified and fixed a bug.

I would take a slightly different route and only wrap the show event.
 
Modify composer.newScene() to wrap scene:addEventListener() to add custom code for the “show” listener.

-- not tested for typos local composer = require("composer") composer.\_newScene = composer.newScene function composer.newScene( ... ) local scene = composer.\_newScene(unpack(arg)) scene.\_addEventListener = scene.addEventListener function scene.addEventListener( self, name ) -- listener arg dropped since it is same as scene if( name == "show" ) then scene.\_show = scene.show local lastDid = 0 function scene.show( self, event ) curTime = system.getTimer() if( event.phase == "did" ) then if( curTime - lastTime \< 10 ) then return end lastTime = curTime self.\_show( self, event ) else self.\_show( self, event ) end end end self:\_addEventListener( name ) end end

Tip - The prior code will ONLY work if you make your scenes like this:

function scene:create( event ) end function scene:hide( event ) end function scene:hide( event ) end function scene:destroy( event ) end scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene )

Thanks for the extra suggestion @roaminggamer.

Trying your code I found 2 typos (1st:  your composer.newScene function needs to return scene object;  2nd:  need to change ‘lastDid’ with ‘lastTime’).

Your solution aims in trapping the 2nd call to the show function in a short period of time (in this case, 10 ms). Testing here, it did avoid the 2nd show call, but it does not avoid a 2nd call to first scene hide function (see log below).

Normal composer behavior (bug):

scene A - on create scene A - on show will scene A - on hide will scene B - on create scene B - on show will scene A - on hide did scene B - on show did scene A - on hide did \<\<-- called TWICE! scene B - on show did \<\<-- called TWICE!

You solution:

scene A - on create scene A - on show will scene A - on hide will scene B - on create scene B - on show will scene A - on hide did scene B - on show did scene A - on hide did \<\< -- called TWICE!

I am using the solution that I posted above and it is working fine. I am trapping all events just because I want to, but I could just trap the “show” event.

welcome.  looks like you no longer need it, but…  you could have used your modifed newScene as the “helper function” i mentioned, might’ve spared having to add the four new listeners

I haven’t seen this come into the ticketing system yet. Have you had a chance to email support AT coronalabs.com with the project.zip and a good description of the problem yet?

Thanks

Rob

I just noticed that the bug also occurs if you call the composer.gotoScene()  from the scene:create(). In that case, not only the show/did function is called twice, but also the show/will.

So, an updated fix would be:

file: rb-composer.lua

local composer = require "composer" composer.\_gotoScene = composer.gotoScene composer.gotoScene = function(sceneName, options) local currScene = composer.getScene( composer.getSceneName( "current" ) ) if currScene then if currScene.\_currEventName == "create" or (currScene.\_currEventName == "show" and currScene.\_currEventPhase == "will") then print("current Scene is on 'CREATE' or 'SHOW - WILL'. Let's not execute the gotoScene now due to composer bug. Let's try again in a few so the scene can be on 'SHOW - DID'") return timer.performWithDelay(50, function() composer.gotoScene(sceneName, options) end) end end composer.\_gotoScene(sceneName, options) end composer.\_newScene = composer.newScene composer.newScene = function() local scene = composer.\_newScene() function scene.trap\_event(event) scene.\_currEventName = event.name scene.\_currEventPhase = event.phase end scene:addEventListener( "create", scene.trap\_event ) scene:addEventListener( "show", scene.trap\_event ) scene:addEventListener( "hide", scene.trap\_event ) scene:addEventListener( "destroy", scene.trap\_event ) return scene end

my guess it that CL will “fix” your bug by amending the documentation to read “Calling gotoScene() while a prior scene transition is still in effect is not support and may lead to unexpected behavior.” or something to that effect (in other words, just citing some version of your original problem description as an unsupported operation).

you might try posting the code that’s CAUSING all these unwanted gotoScene’s that you need to cancel after initiated, then maybe someone could suggest a workaround that prevents the problem before it even occurs.  there would seem to be several ways your “outside module” could submit a *request* for a scene change that is “deferred” (or cancelled outright) if/as necessary by some other “scene manager” -type code capable of monitoring those requests versus current scene states.

I just fell foul of this and spent a large part of a day trying to figure out why I was seeing two ‘did’ phases on a composer show event (and only one ‘will’ phase).

The model I often use is for a scene to load data and in the case where the data does not exist to move onto the next scene. The problematic code for myself was:

[lua]

function scene:show(event)

  if event.phase==“did” then

    return

  end

  local list=_.select(options,function(k,v)

    return v.showFunc()

  end)

  

  if #list==0 then

    composer.gotoScene(event.params.nextScene,{

      params=event.params.nextParams

    })

  end

  …

[/lua]

I think this model allows views to handle the data they need to display while the calling scene does not need to test in advance. I do not use transitions, so the user is unaware. I am quietly sure I have encountered this issue before. Every few years it seems to bite me in the butt. It doesn’t quite occur frequently enough for me to remember it, but it always eats a large chunk of time up before I realise what’s going on.

I just fell foul of this and spent a large part of a day trying to figure out why I was seeing two ‘did’ phases on a composer show event (and only one ‘will’ phase).

The model I often use is for a scene to load data and in the case where the data does not exist to move onto the next scene. The problematic code for myself was:

[lua]

function scene:show(event)

  if event.phase==“did” then

    return

  end

  local list=_.select(options,function(k,v)

    return v.showFunc()

  end)

  

  if #list==0 then

    composer.gotoScene(event.params.nextScene,{

      params=event.params.nextParams

    })

  end

  …

[/lua]

I think this model allows views to handle the data they need to display while the calling scene does not need to test in advance. I do not use transitions, so the user is unaware. I am quietly sure I have encountered this issue before. Every few years it seems to bite me in the butt. It doesn’t quite occur frequently enough for me to remember it, but it always eats a large chunk of time up before I realise what’s going on.