do I really need to have an event handler for URLLoader? - actionscript-3

I am new to actionscript, and I am following a tutorial by Joseph Devore. I understand that when I want to do a network query I can create a URLRequest, and send this with a URLLoader. I expected to find the response data in the URLLoader.data attribute, but after making the query this was still null in my case. Then it seemed that I must create an event handling to actually access the response. Is this true?
I wrote this small example code, and wonder how I (the easiest way) can access the response in this get request.
package
{
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;
public class stackQuery
{
public function stackQuery()
{
}
public function makeQuery(): String {
var request: URLRequest = URLRequest("http://www.google.com");
var loader: URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, handleResponse);
try{
loader.load(request);
}
if(loader.data == null){
return "Empty"
}
return "data not empty"
}
private function handleResponse(event:Event):void
{
//something with event.target.data here??
}
}
}

There isn't any nice way to do a synchronous load (like you can in some other languages) with AS3. Your data is null because in AS3 the load command is asynchronous - meaning it doesn't halt the rest of the application (locking it up) until the load completes, it moves on to the next line of code and dispatches an event once the load is complete.
Here is a code example of how you would want to handle this based off of the code you supplied:
package
{
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;
public class stackQuery
{
private var loader: URLLoader = new URLLoader(); //let's move it out to the class level so you can easily reference it from other methods (though this is purely optional)
public function stackQuery()
{
}
public function makeQuery(): String {
var request:URLRequest = URLRequest("http://www.google.com");
//add you listeners before loading
loader.addEventListener(Event.COMPLETE, handleResponse);
loader.addEventListener(IOErrorEvent.IO_ERROR, handleResponse); //it's good practice to also listen for errors
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleResponse); //this kind of error will happen if you're trying to load a local resource but have published as network only (or vice-versa)
//load the request, any code after this will run before the load completes.
loader.load(request);
}
//this function will run once the load completes
private function handleResponse(event:Event):void
{
if (event is IOErrorEvent) {
//you had an IO error, better do something
return;
}
if (event is SecurityErrorEvent) {
//url wasn't allowed to load
return;
}
trace(loader.data); //this is what was returned from the page
//you could also get it by referencing the currentTarget of the event
trace(URLLoader(event.currentTarget).data);
}
}
}

Yes, of course it is true. Imagine you load one megabyte of data. You need time in order to load it, right? So there's no way that you will have this data on the very next line of code.
The listener will get executed when the target dispatches the event - in your case when that megabyte is loaded.
This is a normal behavior.

While the answers given are correct, the real explanation is that Actionscript 3 is an asynchronous language as opposed to other language like Python for example that are synchronous. In python after loading something like an url the next line of code will not be executed until the url is loaded (which can create lag or idling execution), in Actionscript 3 the code execution doesn't wait and move on to the next line. That is the reason you need to use events (Actionscript 3 is an event based language) so you can find out when the data you are waiting for is available all that while your code is still executing.

Related

Flash Button clicks don't work

I have 4 instances of the same basic button object (a rectangle). The button Class is called LinkButton, with the Name of LinkButton, and each instance is button_1, _2, _3, and _4. I am using the following ActionScript:
import flash.net.navigateToURL;
import flash.net.URLRequest;
import flash.events.Event;
import flash.display.MovieClip;
import flash.events.MouseEvent;
import fl.motion.MotionEvent;
this.button_1.addEventListener(MouseEvent.CLICK, fl_ClickToGoToWebPage("http://google.com"));
this.button_2.addEventListener(MouseEvent.CLICK, fl_ClickToGoToWebPage("http://adobe.com"));
this.button_3.addEventListener(MouseEvent.CLICK, fl_ClickToGoToWebPage("http://amazon.com"));
this.button_4.addEventListener(MouseEvent.CLICK, fl_ClickToGoToWebPage("http://ask.com"));
function fl_ClickToGoToWebPage(url:String) {
navigateToURL(new URLRequest(url), "_blank");
}
When I publish and run from a web server, a new (blank) page opens to google.com - I don't click, it just opens automatically. None of the other pages open, however. And, of course, I really only want these URLs to open on a click event. What have I done wrong here?
The reason for this, is when you add a callback for an event, you pass it a reference to a function.
What you are doing however, is passing the result of the function. (so at the time you add the listener, you are actually running the method fl_ClickToGoToWebPage with the supplied values.)
What you want is this:
this.button_1.addEventListener(MouseEvent.CLICK, fl_ClickToGoToWebPage);
You can pass the data (url) a few different ways.
use a switch statement in the handler:
This solution is pretty good, it's simple though perhaps not as encapsulated as other ways.
//you get a reference to the button that was clicked with the event's current target property.
function fl_ClickToGoToWebPage(event:Event) {
var url:String;
switch(event.currentTarget){
case button_4:
url = "http://ask.com";
break;
case button_3:
url = "http://amazon.com";
break;
//etc for the other buttons
}
navigateToURL(new URLRequest(url), "_blank");
}
Assign a dynamic property to each button.
This solution is the most encapsulated, as the data is attached to the actual button, but dynamic vars are prone to spelling mistakes etc and won't be checked by the compiler.
//if button_1 is a MovieClip or other dynamic class, you can just make up a property on it like this:
this.button_1.url = "http://google.com";
this.button_1.addEventListener(MouseEvent.CLICK, fl_ClickToGoToWebPage);
//..repeat for the rest of the buttons
//then, in the handler, the event.currentTarget is a reference to the button that was clicked:
function fl_ClickToGoToWebPage(event:Event) {
navigateToURL(new URLRequest(event.currentTarget.url), "_blank");
}
Create a class file for your LinkButton and add a public url property
This is most elegant solution, but for just one property it may be a bit overkill. Create a file called LinkButton.as and place it next to your .fla.
Add this to the file:
package {
public class LinkButton extends MovieClip {
public var url:String;
}
}
Though, even better, would be the encapsulate all the functionality of the LinkButton into it's own class file:
package {
public class LinkButton extends MovieClip {
public var url:String;
public function LinkButton(){
this.addEventListener(MouseEvent.CLICK, clickHandler);
}
private function clickHandler(e:MouseEvent):void {
if(!url) return;
navigateToURL(new URLRequest(url), "_blank");
}
}
}
//this way, all you have to do in your timeline is set the url of each button.
Now you have a url property for all LinkButton's, use this property the same way as the previous solution. The difference, is now you have compile time checking.
Use an annoymous function as the return value
This solution is dangerous and prone to memory leaks if you have the need to add listeners dynamically or need to remove/re-add listeners.
The idea is, you call a function with your parameters, that then returns another function used as the handler.
this.button_1.addEventListener(MouseEvent.CLICK, createButtonHandler("http://google.com"));
function createButtonHandler(url:String):void {
return function(e:Event){
navigateToURL(new URLRequest(url), "_blank");
}
}
The problem here, is that you have no way of referencing this function again. As such, it will stay in memory forever even if you remove your button from the screen and null it's placeholders (vars).

addEventListeners and best practices

I'm using this simple code to retrieve a value from a PHP script:
package
{
import flash.display.MovieClip;
import flash.net.URLLoader;
import flash.net.URLVariables;
import flash.net.URLRequest;
import flash.net.URLRequestMethod;
import flash.events.Event;
import flash.events.IOErrorEvent;
//
public class URLTest extends MovieClip
{
//
var scriptLoader:URLLoader;
//
public function URLTest()
{
scriptLoader = new URLLoader();
scriptLoader.addEventListener(Event.COMPLETE, handleLoadSuccessful);
scriptLoader.addEventListener(IOErrorEvent.IO_ERROR, handleLoadError);
}
//;
public function handleLoadSuccessful($evt:Event):void
{
trace("Message sent.");
trace($evt.target.data);
}
//
public function handleLoadError($evt:IOErrorEvent):void
{
trace("Message failed.");
}
//
public function sendLoad(variable):void
{
var scriptRequest:URLRequest = new URLRequest("http://mydomain/myapp/my.php");
var scriptVars:URLVariables = new URLVariables();
scriptVars.var1 = variable;
scriptRequest.method = URLRequestMethod.POST;
scriptRequest.data = scriptVars;
scriptLoader.load(scriptRequest);
trace(scriptLoader);
}
}
}
If you look at the URLTest constructor you'll find I initialize the listeners of an instance of URLLoader.
Is it a better practice to declare a method initListeners and call it everytime I call sendAndLoad? As a consequence, would it be a better practice that of removing the listeners inside the handleLoadError and handleLoadSuccesful methods? Are listeners cpu-intensive?
Btw, I don't understand why this question "appears to be subjective", since we're talking about performances, not programming style.
As your class is designed to be able to make multiple calls to the same webservice, I would definitely go for option 2. Consider this:
var loader:URLTest = new URLTest();
loader.sendLoad("a");
loader.sendLoad("b");
Now, if you re-use the same URLLoader for both calls, there is no way you can know which COMPLETE event is going to correspond to which call.
To solve this issue you would create a URLLoader (+ event listeners) for each call and put it in an Array to be able to identify it. When an answer comes from the server - be it a COMPLETE or an IOERROR - you can remove the event listeners and remove the identified URLLoader (the one that matches event.currentTarget) from the Array.
The performance issue is trivial here. There will be no performance hit for creating a bunch of event listeners as long as you remember to remove them when you're done with them. Otherwise the corresponding URLLoaders will not be garbage collected and will keep piling up in memory.
On a side note: there's absolutely no reason to extend from MovieClip. If you want your class to dispatch events, just extend EventDispatcher.

How can I test to see if a variable has been garbage collected?

I'm trying to set up a test that will tell me whether a variable exists in memory or not. I'm running into the problem of my nested function preserving the local variable it uses, called "shouldBeDead". Here's my best effort, which doesn't work for me because the "shouldBeDead" variable is still alive:
addEventListener(Event.ENTER_FRAME, isDeadYet);
function isDeadYet ($):void {
var shouldBeDead = "not dead";
if (!stage.hasEventListener(KeyboardEvent.KEY_DOWN))
stage.addEventListener(KeyboardEvent.KEY_DOWN, test);
function test($):void {
trace("variable is " + shouldBeDead); // outputs: "variable is not dead"
}
}
Is there a way to test if something exists in memory?
Your String cannot be garbaged collected as there is no new instance created but the value is taken from the constant pool that take care of String, Number, int,etc..
If you create a new Class, Object, Array, etc... these can be garbaged collected and you can trace them with a simple method : putting your instance as a weak key into a dictionnary.
So when garbaged collect occured the key will be deleted from the dictionnary.
Here an example of code for testing, and there live code : http://wonderfl.net/c/uP5T :
import flash.utils.Dictionary;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.system.System;
var notGC:Dictionary=new Dictionary(true)
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown)
function traceNotGC():void{
var cnt:int=0
for (var key:Object in notGC) {
cnt++
trace("not garbaged : " + key)
}
if (cnt==0) trace("All garbaged")
}
function onKeyDown(e:Event):void{
System.gc()
traceNotGC()
}
function test():void{
var str:String="not dead" // string taken from the constant pool
// there is no allocation done
var obj:Object={foo:"bar"} // creation of a new object that can be garbaged
var arr:Array=[0,1,2] // creation of a new array that can be garbaged
notGC[str]=true
notGC[obj]=true
notGC[arr]=true
traceNotGC()
}
test()
Simple technique, similar to Patrick's:
Test with useWeakReferences to See When Object is Garbage Collected
Instantiate custom class containing event handler listening to 'ENTER_FRAME' (use weak reference).
Instance's handler traces output until the instance reference is set to null from the instantiating object.
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class Main extends Sprite {
private var livingObject:LivingObject;
public function Main () {
livingObject = new LivingObject(this, true);
stage.addEventListener(MouseEvent.CLICK, killIt);
}
function killIt ($) {
livingObject = null;
trace('Attempted murder of livingObject.');
}
}
}
import flash.events.Event;
class LivingObject {
public function LivingObject ($main:Main, $immortal:Boolean) {
$main.addEventListener(Event.ENTER_FRAME, proveImAlive, false, 0, $immortal ? false : true);
}
private function proveImAlive ($) {
trace(this + ' LIVES!!!!!'); // Output stops when garbage collected.
}
}
Well first shouldBeDead is not out of scope in the code you listed. the output "variable is not dead" is it's correct state.
In AS3 it is correct for your nested function to preserve that variable.
for your other question.
If the var points at an object you can always check it for object == null (if the garbage collector has gotten it it will be null)
There is something else but I can't remember.
Here is a very clear answer on the scope of that nested function.
Nested Functions, how are they garbage collected in flash actionscript 3?
I don't believe there is a way to do this (or I'm at least not aware of it), because garbage collection is so arbitrary. Here is a good article about the flash player garbage collection. http://www.adobe.com/devnet/flashplayer/articles/garbage_collection.html

AS3 driving me nuts

Ok here is what I am currently trying to do. I have a class called vdata.as which takes 2 paramaters both are strings sent from the main stage. Parameter one is the location for an XML file that I need to open and read. The second parameter is the name of the video I am currently looking for.
Now I can get the data from the XML file and display it with out any issue if its called from my class but when I try to access any of it from the stage I get undefined.
import flash.net.*;
import flash.display.*;
import flash.events.*;
public class videoData
{
private var mName:String;
private var mLink:String;
private var mCategory:String;
public static var elementArray:Array;
// Constructor
public function videoData(xmlPath:String,xmlVidSrc:String,pMC:MovieClip)
{
pXmlPath = xmlPath;
pXmlVidSrc = xmlVidSrc;
xmlloader = new URLLoader();
elementArray = new Array();
}
public function getXML()
{
XMLData();
}
private function XMLData()
{
xmlloader.load(new URLRequest(pXmlPath));
xmlloader.addEventListener(Event.COMPLETE,parseXMLData);
}
private function parseXMLData():void
{
var x:XML = new XML(xmlloader.data);
Init(x);
}
private function Init(m:XML):*
{
var i:Number;
for(i=0; i<m.videos.videoname.length(); i++)
{
if(m.videos.videoname[i].#name == pXmlVidSrc)
{
videoData.elementArray.push(m.videos.videoname[i].#name);
videoData.elementArray.push(m.videos.videoname[i].#category);
videoData.elementArray.push(m.videos.videoname[i].link.#url);
}
}
}
}
When I call it from the main stage the code is as follows.
var xData:videoData = new videoData(xmlPath,vidSrc,this);
xData.getXML();
then when I try to access any elements of videoData.elementArray they come up undefined...
Im just smacking my head on my desk trying to figure this out any help would be great.
Why is elementArray a static var, you only need to make it public to use it outside the function.
I'm quite confusing but you may want to try a debugging tool like "De MonsterDebugger", I would start by tracing xmlloader.data in the parseXMLData function.
"addEventListener" doesn't "fire"...the event does. You'll need to add a boolean to state for the stage that elementArray has been populated and set that after the init function.
Is elementArray something that needs to be true across all instances of videoData? If not, it shouldn't be static. You can use MovieClip(this.root).xData to access that instance of the video class from one of your other classes.
If the event has completed and the array is still empty - then it wasn't populated by your parser. You can also do checks to see if the elementArray.length > 0.
EDIT in response to comment:
as a public member or preferably a read-only property make a boolean variable:
var parseComplete:Boolean;
Set it to false in your constructor.
Then, after your call to "Init" in your Event.COMPLETE callback set:
parseComplete=true;
Then make sure parseComplete == true before you ever access elementArray. If you're waiting for the parser to complete you might want to set a timeout or some sort of try/catch mechanism just in case there are any unforeseen errors that would cause some sort of:
while( !xData.parseComplete ) { }
To loop indefinitely. It all depends on the usage. Personally I'd probably add a callback from the event listener to the stage to trigger whatever is supposed to happen next.

Confused about garbage collection and events with weak references in actionscript 3

I have a reference to an object. This object has a timer event with a weak reference. Example:
timer.addEventListener(TimerEvent.TIMER, timerHandler, false, 0, true);
Now I remove this reference (test is the variable containing the reference):
test = null;
And yet, the timerHandler keeps being fired. Is this impossible, so that I must have somekind of mistake in my code, without any other possibility?
Or is this indeed not supposed to stop the timer function from being run all the time?
The garbage collector doesn't operate continually, so it's likely that it just hasn't run yet. When it finally does, your handler should stop being called. If not, there is probably another reference to it.
When I run the example below, I see timer traced indefinitely, even though handler has been set to null and the EventDispatcher has a weak reference. However, if I force the garbage collector to run by uncommenting the System.gc() line (using the debug player), the handler is never called.
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.system.System;
import flash.utils.Timer;
public class TimerTest extends Sprite {
private var timer:Timer;
public function TimerTest() {
var handler:Function = createHandler();
timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, handler, false, 0, true);
timer.start();
handler = null;
//System.gc();
}
private function createHandler():Function {
return function(e:Event):void {
trace('timer');
};
}
}
}
In general, you shouldn't rely on the garbage collector for the correct operation of your program.