Monday, June 8, 2009

Flex Mate: If you've asked "Why don't my events seem to be getting to the EventMap", this may help. Consider performance though.

I see this a lot out there, so figure a blog post might be very helpful to developers exploring the Flex Mate framework. If you're not familiar with Mate, go to the ASFusion Mate site.

People hear how great and lightweight the Mate framework for Flex development is, and it is interesting to work with (though it does have its issues...however, what framework doesn't). Invariably, every developer crashes into the same problem:

"I have a class that extends EventDispatcher. I dispatch an event from it. The EventMap has the correct type of event registered in its MXML markup, I've got my Manager set up to handle the logic for the event, exactly as it shows in the examples. I then dispatch an event from the Manager. WHY DOESN'T IT WORK the EventMap never seems to see the event ARGH!".

The first thing to note is, there's no explicit listener set up for your event handling. Mate just seems to magically know that you dispatched an event.

Naturally, there's nothing magic about it. The general idea is, if you dispatch an event, it needs to be told how to find a handler. Typically, you just set up an addEventListener for the type of event on the object you want to listen for the event from, then from that object, you dispatch the event. It's a many-to-one thing, but it's explicit. If no object has set up a listener for that event, then even if the event is dispatched, nothing will happen.

However, events have an argument you may have seen: "bubbles". By default, this is set to "false", which is how the vast majority of developers are used to using it. As the name implies, if it's "false", the event will not "bubble".

But, if "bubbles" is set to "true", then the dispatched event will "bubble" up through it's object hierarchy until it finds a handler set up to listen for that event.

From the documentation (note that ancestors mean "objects that came before", e.g. parents):

In the bubbling phase, Flex examines an event's ancestors for event listeners. Flex starts with the dispatcher's immediate ancestor and continues up the display list to the root ancestor. This is the reverse of the capturing phase.

For example, if you have an application with a Panel container that contains a TitleWindow container that contains a Button control, the structure appears as follows:

Application
Panel
TitleWindow
Button

If your listener is on the click event of the Button control, the following steps occur during the bubble phase if bubbling is enabled:

Check the TitleWindow container for click event listeners.
Check the Panel container for click event listeners.
Check the Application container for click event listeners.


So now you get an idea of how the EventMap works. The EventMap is put in the top-level Application (or WindowedApplication for Air developers). So, all events need to "bubble" to the top, so that the EventMap, which has the listeners, will receive them.

So you might think, "great, put in my EventMap listeners, then, just set all my events to bubbles=true and voila."

If only it were that simple; notice in the first line of the documentation quote, it says, "Display List". If you're not familiar, here's the relevant documentation blurb regarding the display list and events:

Every object in the display list can trace its class inheritance back to the DisplayObject class. The DisplayObject class, in turn, inherits from the EventDispatcher class. The EventDispatcher class is a base class that provides important event model functionality for every object on the display list. Because the DisplayObject class inherits from the EventDispatcher class, any object on the display list has access to the methods of the EventDispatcher class.

This is significant because every item on the display list can participate fully in the event model. Every object on the display list can use its addEventListener() method--inherited from the EventDispatcher class--to listen for a particular event, but only if the listening object is part of the event flow for that event.


In other words, if the object you dispatch the event from is not in the display list...so, if your object does not inherit somewhere along the way from DisplayObject, and hasn't been added as a child to some other object that inherits from DisplayObject, which ultimately can trace its DisplayObject ancestors back to level where the EventMap is, it'll never get there.

Why is this a problem? Say you dispatch an event from a DisplayObject that is in the display list that resolves to the EventMap. So far so good, the EventMap will pick it up, and send it to a specified Manager to run a function and complete the handling. Now, from that Manager, after it does something, like set properties on a model or whatever, you want to dispatch an event, the handler for which has been set up in the EventMap. The Manager extends EventDispatcher, the event is set to bubble.

No dice. The EventMap won't see it, because the Manager is not a DisplayObject; it's not in the display list.

The solution: in your non-DisplayObject and/or not-in-the-display-list object, declare a public variable of type GlobalDispatcher (I usually call it "dispatcher"), and either instantiate it in the constructor, or right before you dispatch your event. Use it to dispatch your event. The EventMap will now see it.

If you've never encountered GlobalDispatcher before, it's not that terrible an oversight; it's not a Flex thing, it's a Mate thing. If you've looked at the Mate framework source, you find it in the "core" namespace. The comment in the code indicates:

/**
* GlobalDispatcher is the default dispatcher that "Mate" uses.
* This class functions as a dual dispatcher because we can register to
* listen an event and we will be notified if the event is dispatched in
* the main application and in the SystemManager.
* <p>Because SystemManager is the parent of all the popup windows we can
* listen to events in that display list.</p>
*/

Example code:


public class Manager
{
[Bindable] public var dispatcher : GlobalDispatcher;

public function Manager ( )
{
dispatcher = new GlobalDispatcher ( );
}

public function doSomethingThenDispatch ( data : Object ) : void
{
dispatcher.dispatchEvent ( new Event ( Event.SOMETHING_EVENTMAP_IS_LISTENING_FOR ) );
}
}


Note that because you are using a GlobalDispatcher, Manager doesn't have to extend EventDispatcher. You may want to get a little more OOP about this, inheriting from a base Manager class that has the global dispatcher on it already, and then just either override a function in an extended class or just use the dispatcher parent instance directly, whatever floats your boat.

I wonder about this kind of global dispatching and the wisdom of it from a performance perspective; I suspect that bubbling all your events through the entire display list in an application can degrade performance, and that seems to be a supported notion. Take a look at this quote from the April 2009 issue of Adobe's Edge newsletter:

There are performance considerations with this approach. Because we are relying on bubbling to communicate our event all the way up the Display List, Flash Player must broadcast an event all they way through the ancestor chain of the component that dispatched the event. If our component is buried deep inside many HBoxes, VBoxes, and Canvas containers, then our event could bubble through hundreds of objects before it reaches the top. If our application is communication-heavy, the overall performance of the application could degrade when a large number of messages need to be passed on. This is a factor developers must consider when choosing this solution

An experienced Flex/Air developer may say, "well of course. Don't build apps that nest heavily. Break up your event maps. Only bubble events that are logical to bubble". Fact is though, people use frameworks because they want a prescribed model; the EventMap is EASY. So just nest your containers based on what works fast, and bubble the events to the EventMap. Bang. The bigger your app gets, the more this will degrade performance, until you eventually top out some ceiling and it all comes down.

So, it would seem that a best practice for Mate would be, even more so than usual...CAREFULLY NEST CONTAINERS, and, don't use the EventMap, or numerous EventMaps, for all your events. ONLY bubble events that are otherwise not practical to set up dedicated listeners for, i.e. for components that you would might consider taking the easy way out with things like "this.that.that.that.addEventListener", and that sort of thing. There's no reason that you can't use the standard ActionScript model, and the Mate model, to get the best of both worlds and build a solid application.

As always, thanks for visiting.

3 comments:

  1. Thanks, this was exactly what I was looking for.

    I'm new to using mate. Does most of your application logic end up in the manager classes?

    ReplyDelete
  2. thank you so much for your post.
    I really appreciate a lot

    ReplyDelete
  3. Nice! For half a terrible second I thought I would have to make my controller class extend from Group and add it to the display list. Such a gross idea. This makes much more sense.

    As far as bubbling up through the Display List, this only seems to happen if the object is part of the Display List. If you have a controller class that utilizes GlobalDisptacher and a deeply nested component triggers an event dispatch via this controller, the event does not bubble up through that components ancestry, since the component did not dispatch the event the controller did. At least my initial testing seems to indicate this. In this case then utilizing a controller is utterly sensible because it is clean separation of presentation and logic and seems to avoid having to bubble the event all the way up the Display List.

    ReplyDelete