Program Flow and Event Listeners in ActionScript - actionscript-3

I'm having difficulty with program flow control when XML loads from an external source. I'm using Flash Builder 4.6
What should happen: use a loader. Listen for when it is loaded. When it fires the COMPLETE event, proceed.
What is happening: use a loader. Listen for when it is loaded. But before the COMPLETE event fires, program control returns to the calling class. So meanwhile, the program keeps going and throws an error because it is trying to access data which is null.
Three classes are involved:
AdvocacyWiz.mxml - the base MXML file for this AIR mobile app
Model.as - the model class
myXMLLoader.as - class in which the loader loads data, then
dispatches event when it's done.
I am calling a function setUpModel() in AdvocacyWiz.mxml once AdvocacyWiz.mxml is added to the stage. The setUpModel function in that class just looks like this:
Model.Instance.initialize(); //model is a singleton
The initialize function call invokes a loader in my XMLLoader.as class. That's where the event listener is left listening for the Complete event, which happens too late to prevent a null error from occurring.
EDIT: Here's the code --
In AdvocacyWiz.mxml (this fires first):
protected function addedToStageHandler(event:Event):void
{
setUpModel();
stage.scaleMode = StageScaleMode.NO_SCALE;
//... additional layout functions
private function setUpModel():void {
Model.Instance.initialize();
}
In Model (this fires second):
public function initialize():void {
addEventListeners(); //this includes listener for StoriesXMLLoader.STORY_LOADED event which will be dispatched from the loader class.
useExternalXML();
...
}
private function useExternalXML():void
{
myLoader.getStory("1140");
}
In the Loader class:
public function getStory(storyId:String):void {
var url:String = "http://mysite.com/whatever.xml";
myLoader.dataFormat = URLLoaderDataFormat.TEXT;
myLoader.addEventListener(Event.COMPLETE, storyXMLLoaded);
myLoader.load(new URLRequest(url));
}
private function storyXMLLoaded(e:Event):void {
storyXML = new XML(e.target.data);
dispatchEvent(new Event(StoriesXMLLoader.STORY_LOADED));
}
What I want to do is call setUpModel() but not have flow returned to the mxml class until the model actually has data.
Thanks.

Well, you haven't provided any code, which would help considerably, but here's a few pointers.
What should happen: use a loader. Listen for when it is loaded. When
it fires the COMPLETE event, proceed. What is happening: use a loader.
Listen for when it is loaded. But before the COMPLETE event fires,
program control returns to the calling class. So meanwhile, the
program keeps going and throws an error because it is trying to access
data which is null.
A lot of Flex is asynchronous. What this means is that when you try to load from the external source, program control immediately returns, as you noted.
In order to have the kind of program flow you want, you need to have the function that calls load() end after that call. Then, when the COMPLETE event listener fires, you can execute the remainder of the code that causes the null exception.

Related

AS3: Error while accessing a function of an externally loaded swf file

I get the following error:
Error #1069: Property externalFun not found on LoadedFile and there is no default value.
My main project file:
questionLoader = new Loader();
var questionRequest:URLRequest = new URLRequest(xmlName);
questionLoader.load(questionRequest);
questionLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, swfLoaded);
public function swfLoaded(e:Event) {
var target: MovieClip = e.currentTarget.content;
trace(target);
addChild(target);
target.externalFun();
}
Loaded swf:
public function LoadedFile() {
// constructor code
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
public function externalFun():void {
trace("IT WORKS");
}
Any ideas?
Listen to Event.INIT rather than Event.COMPLETE. Event.COMPLETE happens immediately after all bytes are loaded and does not indicate that loaded content is actually available already. If you care to put one trace before calling externalFun and the other into the LoadedFile constructor, the latter one will fire later than the former. However, Event.INIT occurs after the loaded content is initialized thus the problem probably will go away. If no, proceed with trace diagnostics described above and probably wait 1 frame after Event.INIT fires before calling the function you want.

"Warning: 1090: Migration issue" despite explicitly registering event handlers

I have a game engine class that inherits from MovieClip and handles mouseDown events in a private instance method. For the sake of simplicity, I name the event handler onMouseDown. It looks like this:
private function onMouseDown(e:MouseEvent):void
{
if (_isEnginePlaying)
{
_player.attack();
}
}
I register it in the engine class's init method (itself an addedToStage handler) that looks like this:
private function init(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// ...
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
// ...
}
This compiles and works correctly, but the compiler warns:
Warning: 1090: Migration issue: The onMouseDown event handler is not triggered automatically by Flash Player at run time in ActionScript 3.0. You must first register this handler for the event using addEventListener ( 'mouseDown', callback_handler).
But, as shown, I did register the handler using addEventListener(). Why does the compiler still emit this warning and what can I do to make the warning go away?
This is because the handler was registered with a different instance and not the game engine instance (this). Remember that event handlers in AS2 were registered simply by specifying them as properties on the instances that should handle the respective events and will automatically fire when needed. The warning is there to inform the developer that they do not automatically fire in AS3.
So the compiler errs on the side of caution, assumes I'm trying to register a click handler with the game engine instance (even though I've already registered it with the stage) and warns me to that effect.
There are a number of ways to make the warning go away:
Just rename the onMouseDown handler. The "on-" convention is a holdover from AS2 and the compiler only emits warnings for handlers following this naming convention; if you're comfortable settling for a different convention, this is the recommended solution. The convention used at Adobe is "-Handler" (sources 1 2 3), so onMouseDown becomes mouseDownHandler:
private function init(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// ...
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
// ...
}
private function mouseDownHandler(e:MouseEvent):void
{
if (_isEnginePlaying)
{
_player.attack();
}
}
If this can listen for the event, register the event with this instead:
private function init(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// ...
addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
// ...
}
If the intention was for this to listen for the event, then registering the handler with a different instance was inappropriate.
If you need to register the handler with a different instance, but you're sure that the definition is where it should be, you'll have to rename the handler. If for whatever reason changing the naming convention across all of your event handlers is not an option, the least you should do is prefix the name of whatever you're registering it with, so it's clear that this handler is not intended to be registered with this, but with a different instance:
private function stage_onMouseDown(e:MouseEvent):void
{
if (_isEnginePlaying)
{
_player.attack();
}
}
If you insist on keeping things the way they are, you can suppress the warning altogether, but obviously this is not something you should be doing unless you have a very good reason to.

Custom Event Listener not working

I am new to as3 and I recently saw creation of custom events in as3 in a tutorial and I wanted to incorporate in my game. When i did the tutorial, it all seemed well for that project. But it doesnt seem to work with the new project.
Here is my code :
package
{
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
public class FashionFrenzy extends MovieClip
{
public var Buyer_mc:Buyer;
public var Buyers:Array;
public var gameTimer:Timer;
public function FashionFrenzy()
{
GameTimeController();
GenerateBuyers();
addEventListener(ReachMallDoorEvent.CHECK, OnReachMallDoor);
}
public function GameTimeController()
{
gameTimer = new Timer( 25 );
gameTimer.start();
}
public function GenerateBuyers()
{
Buyers = new Array ;
Buyer_mc = new Buyer(533.2,0) ;
addChild(Buyer_mc);
gameTimer.addEventListener( TimerEvent.TIMER, BuyerEnter );
if(Buyer_mc.y==377.25)
{
dispatchEvent( new ReachMallDoorEvent( ReachMallDoorEvent.CHECK ) );
}
}
public function BuyerEnter(event:TimerEvent)
{
Buyer_mc.Enter();
}
public function OnReachMallDoor(event:ReachMallDoorEvent)
{
trace("my timer starts now");
}
}
}
Here, OnReachMallDoor never seems to run because there is something wrong. I cant see the output saying "My timer starts now". But there is no error in the code and output doesnt show any runtime errors either. Where have I gone wrong? I want OnReachMallDoor function to run when my y coordinate is in desirable position and the event is dispatched.
The order of commands is wrong.
GenerateBuyers();
addEventListener(Rea...
The first line of these two is the one that could potentially cause the Event to be dispatched. But only after that will you start listening for it. That's simply too late. You have to start listening before the Event is dispatched.
The probability of the Event to be dispatched is very low.
Buyer_mc.y==377.25
Checking floating point values for equality is often not a good idea. It could easily be just slightly off due to rounding errors etc. If this .y property was controlled by the mouse for example, you'd have to position the mouse at exactly that position, which is very unlikely.
You only dispatch the Event at the beginning.
GenerateBuyers();
That function is only called once. The .y position is evaluated.
This only happens once and never again.
But the .y position is subject to change and the condition should be evaluated again, which doesn'T happen
The structure is not helpful.
It doesn't make much sense for an object to listen for its own Events. Simply call the function and be done with it.
Events are for communication between objects.
How this is supposed to be:
The point of the custom Event is to be notified about something.
You want to be notified when this condition
Buyer_mc.y==377.25
is true.
If you are evaluating that condition the way you do it now, then there's no point in receiving a notification about the result thereof. You have it already.
Instead, Buyer_mc should dispatch the Event. The condition should be checked in Buyer class.
What the code looks like
some snippets pointing out what the above means, code untested:
class Buyer
override public function set y(value:Number):void
{
if (value == 377.25)
dispatchEvent(new ReachMallDoorEvent(ReachMallDoorEvent.CHECK)); // I'm at the position, I send out the notification
super.y = value;
}
class FashionFrenzy
buyer = new Buyer(533.2, 0); // variables should start with small letter
buyer.addEventListener(ReachMallDoorEvent.CHECK, OnReachMallDoor);
If you now set the .y position to the value, the object will dispatch the Event. It will figure that out on its own.
Letting the object figure something out on its own and just receive a notification about it is the main reason to use custom events.

Robotlegs - second dispatch does not work with addViewListener

I have a mediator created in Robotlegs, and its related view would dispatch two different kinds of events. When the mediator captures the event, it would just simply dispatch the event. The problem I ran into was that the first event is re-dispatched flawlessly, but the 2nd event was not dispatched.
However, if I manually assign a different handler to the 2nd event, the event is properly captured.
Below is the relevant code:
public class MyMediator extends Mediator
{
[Inject]
public var view:MyView;
public override function onRegister():void
{
super.onRegister();
addViewListener( SomeEventTypeA.COOL_EVENT, dispatch, SomeEventTypeA ); // This event is dispatched correctly
addViewListener( SomeEventTypeB.STUCK, dispatch, SomeEventTypeB ); // This one is not correctly dispatched
//A twist, if I uncomment the following code, the event is captured by its handler
//addViewListener( SomeEventTypeB.STUCK, view_stuck, SomeEventTypeB );
}
private function view_stuck( event:SomeEventTypeB ):void
{
//ah ha, this is called correctly if the above relevant line is uncommented
}
}
Found the cause:
The event needs to have a proper clone method in order to be re-dispatched correctly. See related link:
http://knowledge.robotlegs.org/kb/application-architecture/why-doesnt-my-event-trigger-the-command-it-is-mapped-to

Selecting Input TextField throws Security sandbox violation in loaded swf in Adobe AIR

I have a swf, loaded into the non-application sandbox in Adobe AIR 1.5 (the shell is already installed with our users so I can't update to version 2+).
On the stage in the swf are buttons, movieclips, animations etc - all of these work fine.
When we add an input TextField, selecting this TextField causes a Security Sandbox Violation.
Error message (in debug mode) is (I've edited the actual file names):
[trace] *** Security Sandbox Violation ***
[trace] SecurityDomain 'file:///path/to/local/loaded.swf' tried to access incompatible context 'app:/loadingApp-debug.swf'
The user then is unable to enter text into the TextField. The rest of the application is unaffected.
The FDB stacktrace only shows:
this = [Object 57216577, class='flash.utils::Timer'].Timer/tick() at <null>:0
Has anyone got a workaround for this?
I'm guessing it's either the TextField attempting to access the stage, or an event attempting to bubble / access global properties.
I understand the air sandbox restrictions and use them daily - with sandboxBridges from parent to child and child to parent etc - perhaps there is something I need to expose to allow this to work?
Any clues?
Edit:
I've now tracked down the problem to being that the TextField attempts to do
this.stage.focus = this;
or something equivalent when MouseDown happens.
It also appears that there is no access to KeyboardEvents in loaded swfs, so my thought of making the 'field' a button and then controlling input by listening to KeyboardEvents is dead in the water.
Now looking at whether to relay events to callbacks passed through the parent sandbox bridge, or whether minimal comps might save my butt.
Ok, I have an insane workaround, but it's pretty solid. I'm going to post it almost in full here, though I'll probably make it generic and upload it to github at some point.
In my shell, I have a view-with-mediator (I'm using robotlegs) which I'm calling EventRelayer and EventRelayerMediator.
The view's only purpose is to give the mediator access to the stage.
I exposed some functions on the parentSandboxBridge:
public function requestKeyboardEventRelay(eventType:String, callback:Function):void;
public function requestMouseEventRelay(eventType:String, callback:Function):void;
public function cancelKeyboardEventRelay(eventType:String, callback:Function):void;
public function cancelMouseEventRelay(eventType:String, callback:Function):void;
My sandbox bridges always just translate into strong typed events, so these fire events like:
RelayEvent(RelayEvent.START_RELAY_REQUESTED, KeyboardEvent, eventType, callback);
RelayEvent(RelayEvent.CANCEL_RELAY_REQUESTED, MouseEvent, eventType, callback);
These are picked up by the EventRelayerMediator, and translated into handlers in an eventMap:
override public function onRegister():void
{
createRelayHandlerFactories();
eventMap.mapListener(eventDispatcher, RelayEvent.START_RELAY_REQUESTED, startRelay);
}
protected function startRelay(e:RelayEvent):void
{
var handler:Function = createRelayHandler(e.relayEventClass, e.callback);
eventMap.mapListener(view.stage, e.relayEventType, handler, e.relayEventClass);
}
protected function createRelayHandler(relayEventClass:Class, callback:Function):Function
{
var handler:Function = relayHandlerFactoriesByEventClass[relayEventClass](callback);
return handler;
}
protected function createRelayHandlerFactories():void
{
relayHandlerFactoriesByEventClass = new Dictionary();
relayHandlerFactoriesByEventClass[KeyboardEvent] = createKeyboardEventRelayHandler;
relayHandlerFactoriesByEventClass[MouseEvent] = createMouseEventRelayHandler;
}
protected function createKeyboardEventRelayHandler(callback:Function):Function
{
var handler:Function = function(e:KeyboardEvent):void
{
trace("Relaying from shell: " + e.toString());
// passing an object because the sandbox bridge doesn't allow strong typed values, only primitives
var o:Object = {};
o.type = e.type;
o.charCode = e.charCode;
o.keyCode = e.keyCode;
o.altKey = e.altKey;
o.ctrlKey = e.ctrlKey;
o.shiftKey = e.shiftKey;
// no point adding other props as we can't pass them
// to the constructor of the KeyboardEvent
callback(o)
}
return handler;
}
The loaded swf passes a callback which just re-assembles and re-dispatches the events.
My input TextField is now just a dynamic field with a click handler that activates listening for keyboard events on the root of the swf, and then updates the dynamic field accordingly.
At the moment that is super-crude but I'll break it out into a robust, tested class now I know it works.
I've used a dictionary to manage the handlers because I'm sure that memory leakage hell is about to follow and I'm expecting to have to relay the FocusEvents to stop entering text.
I need to test memory leakage, return a binding from the parentSandboxBridge function so that I can make sure I don't add the same handler twice etc etc, but Adobe - you suck for not calling this out and providing a built in relay mechanism.