8 Replies Latest reply on Jul 8, 2012 4:04 PM by tjnelso

    Not Understanding the Event Dispatcher/Waiting for Event

    tjnelso

      I am trying to load data from a .txt file and have it read to a string. I then want to parse the string and place the data into an array. My problem is I call a function to load the data with a URLLoader and then once the data is loaded I need to place it in a string and parse that to an array. But the whole 'once the data is loaded' idea isn't exactly what Flashbuilder is apparently designed to do. So I have the following code that is called and I am trying to get to return a string containing the text from the text file (the parameter 'location' contains the address for the .txt file, which is in the bin-debug folder with the project .swf file):

       

      package actionscript
      {
           import flash.display.Sprite;
           import flash.events.Event;
           import flash.events.IOErrorEvent;
           import flash.net.URLLoader;
           import flash.net.URLRequest;

           import mx.collections.ArrayCollection;

       

           public class FileLoad extends Sprite
           {
                  private var loader:URLLoader;
                  private var request:URLRequest;
                  private var text:String;
                  private var loaded:Boolean = false;
                  private var textArray:ArrayCollection;
       
       
                  //------------------------------------------------------------------------------
                  //Creates an object that can load a text file saved in the bin-debug folder of
                  //the 'Project' Flex Project
                  //------------------------------------------------------------------------------
                  public function FileLoad()
                  {
                        super();
                        loader = new URLLoader();
                  }
       
       
                  //------------------------------------------------------------------------------
                  //Loads the specified text file saved in the bin-debug folder of the 'Project'
                  //Flex Project
                  //------------------------------------------------------------------------------
                  public function retrieve(location:String):String
                  {
                        request = new URLRequest(location);
                        loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
                        loader.addEventListener(Event.COMPLETE, loadComplete);
        
                        trace("test1");
        
                        try
                        {
                              loader.load(request);
                        }
                        catch(error:SecurityError)
                        {
                              trace("A Security Error has occured.");
                        }
        
                        trace("test3");
                        trace(text);
        
                        return text;
                  }

       
                  //------------------------------------------------------------------------------
                  //Once the constructor completes loading the text file, this method creates a
                  //text representation of the data and adds it to the main application panel
                  //------------------------------------------------------------------------------
                  private function loadComplete(event:Event):void
                  {
                        loaded = true;
        
                        try
                        {
                              text = event.target.data;
                         }
                        catch (e:TypeError)
                        {
                              text = "Could not parse the file.";
                        }
        
                        trace("test2");
                        trace(text);
                  }
        
       
                  //------------------------------------------------------------------------------
                  //If the constructor throws an error loading the text file this method adds an
                  //error notification to the text field on the main application panel
                  //------------------------------------------------------------------------------
                  private function errorHandler(e:IOErrorEvent):Boolean
                  {
                        return loaded;
                  }
       
           }
      }

       

       

       

      Of course since Flash isn't waiting for the eventListener to register the event before moving on with the code, the 'text' string returns a null value. I have found online that some people deal with this by creating a custom event class and using the eventDispatcher. I don't understand how this can solve the problem though, isn't the bottom line that we need to listen to an event and no matter what Flash will skip on to whatever code it can instead of waiting for the event to process?

       

      I guess I also don't understand eventDispatcher. How is dispatching an event going to help solve the problem of flash not waiting for another event? If anyone can just explain the intuition behind this solution I might be able to figure out the code examples I've found online.

       

      Thanks.

        • 1. Re: Not Understanding the Event Dispatcher/Waiting for Event
          Gaius Coffey Level 2

          I posted a working example in your other thread...

          Basically, events fire when the thing you are waiting for has happened. If you choose the right event, you will get the result you want.

          The retrieve function returns the text at the point of making the load request, rather than at the point of receiving data. Not sure why you are returning a Boolean from your errorHandler function either, as that will never be seen or used in the code as you have it.

          To do what you want, ou just need to expand your loadComplete function to parse the received text into an Array which you can then use as source for your array collection.

          For example, if your text was a comma separated list of values:

          var a:Array = text.split(","); // returns an Array by cutting at each comma in the String

          Var ac:ArrayCollection = new ArrayCollection(a); // uses Array as .source for new ArrayCollection

          • 2. Re: Not Understanding the Event Dispatcher/Waiting for Event
            Gaius Coffey Level 2

            Ps: You'll see I've used [Bindable] on one of the vars in my other example, that allows Flex to pick up the data change when you write to the var, which is why the change is then reflected in the TextArea.

            • 3. Re: Not Understanding the Event Dispatcher/Waiting for Event
              tjnelso Level 1

              Gaius, thanks again for the reply. I think I get where you're going with the Array being included in the loadComplete function, but I'm not quite there. In my main Flex application I created a method that is called through the creationComplete instantiation, as you suggested in my other post. The function that is then called in my main application creates an instance of the FileLoad class and calls its retrieve method to populate a string in the main application, something like this:

               

              [CODE]

              private var dataString:String;

               

              var dataAccess:FileLoad = new FileLoad();

              dataString = dataAccess.retrieve(location);

              [/CODE]

               

              The idea was to return a string from the retrieve method to instantiate the dataString variable with the data from my .txt file. I was then planning to use the dataString variable to populate an Array/ArrayCollection object. So it sounds like what you are suggesting is just moving that work to the retrieve method, which I agree would be cleaner. What I'm still unsure on, though, is how to get the retrieve method to return something to the main application after the data is loaded, so it can populate the string/array/arraycollection that I want to use outside the FileLoad class to populate charts/etc. However, I have thought of another idea and am wondering if this is valid, instead:

               

              ----------------------------- MAIN APPLICATION (inside the creationCompleteHandler function) -------------------------

               

              private var dataArray:ArrayCollection;

               

              //Have the constructor take the file location as a parameter

              var dataAccess:FileLoad = new FileLoad(location);

              //call the retrieve method, which now takes an ArrayCollection object as a parameter

              dataAccess.retrieve(dataArray);

               

               

              ---------------------------------------------------------- FileLoad Class -------------------------------------------------------------

               

              package actionscript
              {
                   import flash.display.Sprite;
                   import flash.events.Event;
                   import flash.events.IOErrorEvent;
                   import flash.net.URLLoader;
                   import flash.net.URLRequest;

                   import mx.collections.ArrayCollection;

               

                   public class FileLoad extends Sprite
                   {
                          private var loader:URLLoader;
                          private var request:URLRequest;
                          private var text:String;

                          private var url:String;
                          private var loaded:Boolean = false;

               
                          //--------------------------------------------------------------------------------------- -------
                          //Creates an object that can load a text file saved in the bin-debug folder of
                          //the 'Project' Flex Project
                          //--------------------------------------------------------------------------------------- -------
                          public function FileLoad(location: String)
                          {
                                super();

                                url = location;
                                loader = new URLLoader();
                          }
               
               
                          //--------------------------------------------------------------------------------------- ------
                          //Loads the specified text file saved in the bin-debug folder of the 'Project'
                          //Flex Project
                          //--------------------------------------------------------------------------------------- ------
                          public function retrieve(dataArray:ArrayCollection):void
                          {
                                request = new URLRequest(url);
                                loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
                                loader.addEventListener(Event.COMPLETE, loadComplete)

               

                                try
                                {
                                      loader.load(request);
                                }
                                catch(error:SecurityError)
                                {
                                      trace("A Security Error has occured.");
                                }

                          }

               
                          //--------------------------------------------------------------------------------------- ---------
                          //Once the retrieve method completes loading the text file, this method creates an
                          //ArrayCollection representation of the data and adds it to the main application
                          //--------------------------------------------------------------------------------------- ---------
                          private function loadComplete(event:Event):void
                          {
                                private var tempArray:Array;

                                loaded = true;
                
                                try
                                {
                                      text = event.target.data;

                                      tempArray = text.split(",");

                                      dataArray = new ArrayCollection(tempArray);

                                 }
                                catch (e:TypeError)
                                {
                                      text = "Could not parse the file.";
                                }
                
                                trace("test2");
                                trace(text);
                          }
                
               
                          //--------------------------------------------------------------------------------------- -------
                          //I know this does nothing useful right now, it will be changed.
                          //--------------------------------------------------------------------------------------- -------
                          private function errorHandler(e:IOErrorEvent):Boolean
                          {
                                return loaded;
                          }
               
                   }
              }

               

              [/CODE]

               

              Now, if I recall correctly, modifying the dataArray instance of ArrayCollection within the FileLoad's retrieve method will cause it to also be modified in the main application, right? I'm still not confident this is a fix though, because in the retrieve method control will enter the try clause and begin loading the data with the loader.load(request) command. However, it still won't wait for the event to complete loading, so control will jump back to the main application and go on processing whatever is there before the dataArray object points to anything other than null. Don't I need control to actually wait for the loading to finish (wait for the loadComplete method to be called) before returning to the main application so that when control does return to the main application the dataArray object will be populated?

               

              One possible solution I'm thinking of is to include "loadInProgress" (this being the initial state) and "dataLoaded" states in the main application and essentially have the program wait in a while lope for the dataAccess variable to populate. Would the following be valid:

               

              ----------------------------- MAIN APPLICATION (inside the creationCompleteHandler function) -------------------------

               

              [CODE]

              private var dataArray:ArrayCollection;

               

              var dataAccess:FileLoad = new FileLoad(location);

              dataAccess.Retrieve(dataArray);

               

              while(dataArray == null)

              {

                   currentState = "loadInProgress";

              }

               

              //In this state the charts/etc that use the .txt file data will be included so they can populate with the data

              currentState = "dataLoaded";

               

              [/CODE]

               

              Sorry, I would experiment with these ideas in FlashBuilder but I'm not anywhere I currently have access. I'm kind of doubtful of these ideas though as solutions offered up in other forums seem more complicated. If these ideas are wrong and anyone can explain to me why it would be greatly appreciated. Thank you again Gaius for your help.

              • 4. Re: Not Understanding the Event Dispatcher/Waiting for Event
                Gaius Coffey Level 2

                >> I posted a working example in your other thread...

                 

                Please take a look at the way I did it in your other thread. Start with mine, which works, and then vary it to see why.

                 

                For reference, the reason the other example didn't work is because you haven't added anything to the stage for display - your loader class added a TextField to itself, but remains in memory. However, you _can't_ add the home-cooked loader directly to the Application as a child in a Flex app as it is a plain Sprite and doesn't implement any of IVisualElement or IUIDisplayObject... Moreover, there is absolutely no need to, as you will see if you look at my working example.

                 

                As a side-note, I said almost the exact opposite to what you have understood about the retrieve function - loading is asynchronous, so your retrieve function can _never_ return the current contents of your text file unless the text file was previously loaded in another call...

                G

                • 5. Re: Not Understanding the Event Dispatcher/Waiting for Event
                  Gaius Coffey Level 2

                  Right, I have a bit of time while I wait for an iOS compile to complete and it's clear you need a bit more of an understanding of what is going on.

                   

                  1. Code defined in a function executes as a single block when that function is called.

                  2. Code defined on a Flash keyframe executes as a single block when that frame is entered.

                  3. Event handlers are defined as functions - they execute as a single block when the event occurs.

                   

                  What you have done in the loop here is to set up an infinite loop:

                  [CODE]

                  while(dataArray == null)

                  {

                       currentState = "loadInProgress";

                  }

                  [/CODE]

                   

                  dataArray will _never_ be set as there is no code within the loop to set it. The Flash movie will reach that block of code and then loop continually until the Flash plugin gives up and tells it to halt... during this time, your computer will be unresponsive.

                   

                  Instead, what happens (or should happen) is this:

                  1. You instantiate your object (URLLoader) and add an event listener.

                  2. You call the .load() method which starts things going...

                  3. The function exits and you go away and do something else... Timeline continues, user can make some coffee... whatever.

                  4. When and only when the loader has received data, it will call the event handler.

                  5. Code inside the event handler is then used to respond to the data that you _finally_ have.

                   

                  This is asynchronous processing - the response occurs at an indeterminate (but possibly very long) time after the initial request was made. For example, it could take a minute or two for a very large file to download...

                  G

                  • 6. Re: Not Understanding the Event Dispatcher/Waiting for Event
                    tjnelso Level 1

                    Gaius, thanks again for explaining things to me. I have used your working code from the other thread and see that things are executing fine. I am obviously a little new to this stuff, though, and am wondering how to modify your working code to get things the way I'd like. So right now I have a main application that loads data from a text file and populates an arrayCollection object with that data.

                     

                    This main application also instantiates an object using MXML that controls all the UI (id="uiComponents") and the uiComponents object instantiates several charts using MXML. I want those graphs to be populated with the data from the .txt file. How do I get that data, which sits in an arrayCollection object in the main application, to the charts?

                     

                    The reason I wanted to be able to create a FileLoad class that could have a url passed in and an arrayCollection object passed out is so that I could call it within each chart object to populate the series. It seems that with your working code I would have to paste that code in each chart since I can't call an object that will return the data.

                    • 7. Re: Not Understanding the Event Dispatcher/Waiting for Event
                      Gaius Coffey Level 2

                      Any number of ways to achieve that... I were you, I'd concentrate on understanding the basics first.

                       

                      However...

                       

                      Here's one approach:

                      [code]

                      package  {

                          import flash.net.URLLoader;

                          import flash.net.URLRequest;

                          import flash.events.Event;

                          public class LazyURLLoader extends URLLoader {

                              public function LazyURLLoader() {

                                  super(null);

                              }

                              public function loadThenCall(request:URLRequest,thenCall:Function,additionalData:Array=null):void {

                                  super.load(request);

                                  var tgt:Object = arguments.callee;

                                  var ul:LazyURLLoader = this;

                                  var otherInfo:Array = additionalData==null ? new Array : additionalData;

                                  var fn:Function = function(event:Event):void {

                                      removeEventListener(Event.COMPLETE,fn);

                                      otherInfo.unshift(ul.data);

                                      thenCall.apply(tgt,otherInfo);

                                  };

                                  addEventListener(Event.COMPLETE,fn);

                              }

                          }

                      }

                      [/code]

                       

                      Then in your MXML...

                       

                      [code]

                      protected function setupFieldLoaders():void {

                           var lazy:LazyURLLoader;

                           var fileName:String;

                       

                           fileName = "data1.txt";

                           lazy = new LazyURLLoader();

                           lazy.loadThenCall(new URLRequest(fileName),processReceivedData,[dataGrid1,fileName]);

                       

                           fileName = "data2.txt";

                           lazy = new LazyURLLoader();

                           lazy.loadThenCall(new URLRequest(fileName),processReceivedData,[dataGrid2,fileName]);

                      }

                      protected function processReceivedData(data:String,dg:DataGrid,fileName:String):void {

                           trace("processReceivedData from fileName "+fileName);

                           var a:Array = data.split(",");

                           var ac:ArrayCollection = new ArrayCollection(a);

                           dg.dataProvider = ac;

                      }

                      [/code]

                      • 8. Re: Not Understanding the Event Dispatcher/Waiting for Event
                        tjnelso Level 1

                        I am definitely beginning to understand the asychronous programming attributes of Flex and see that it is impossible to do what I was describing, i.e. have the code wait to process until the listener finishes listening. I was looking for a way to work around that feature of ActionScript to get the program to do what I needed and the code you posted in your last comment I see will do that just fine. I am glad you posted that code because it uses a lot of functions that seem unique to ActionScript that I didn't know about so I'm glad I have now been exposed to them.

                         

                        I'm wondering though, since I have a .txt file with a large amount of data, when I process the data and import it into Flash Builder it will be contained in one Array Collection with the indices of the array essentially containing a linear representation of the data. It seems though that to populate charts I need to format the data in a specific way, generally using an array of arrays, in which each subarray contains all of the data corresponding to one point on the x-axis (Each subarray contains the x and y values for a specific point, with as many subarrays as their are points). I could use for loops to convert my single arrayCollection into the format needed to populate these charts, but it seems ActionScript also provides data models that would provide the same functionality more efficiently. From a high-level perspective, what strategy should I employ to manipulate the data in my arracyCollection so that it is in the format needed to be used in column charts and line charts? Should I just use for loops and such to put the data in new arrays in the format I need it, should I create a data model that can format the data, or should I use arrayCollecton methods such as filter() and sort()?

                         

                        I'm sure there is more than one way to do what I'd like but I am trying to figure out what would be most efficient from a learning and loading perspective.

                         

                        Thanks.