August 31, 2006

Binding Warning

While working on an AS3 class today I had the following message appear in the debug console:

warning: unconverted Bindable metadata in class 'com.example::SampleData'

Everything still seemed to work correctly, but the fact that the message was getting reported concerned me. Unfortunately, this message doesn't appear in the run-time error documentation, which makes some sense since there was no error number associated with the problem and it just a warning. Doing a little more digging I found that the message is coming from the class mx.binding.BindabilityInfo.as. Thank you Adobe for including sources with the framework!

It seems that when the binding is being resolved the class metadata is bad. In this case the metadata was:

<metadata name="Bindable"/>
<metadata name="Bindable">
<arg key="event" value="propertyChange"/>
</metadata>

The message results from the fact that the first Bindable metadata entry doesn't include an event name. The more important thing was what was causing the event not to exist? Turns out it was a combination of the ordering of my get/set methods and the fact that I was casting my object as an Object instead of as SampleData. A little code will help explain what was going on. First this is the simple application that uses the SampleData instance.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:example="com.example.*">
<mx:Script>
<![CDATA[
import com.example.SampleData;
[Bindable]
private var _data:Object = new SampleData();
]]>
</mx:Script>
<mx:Label text="{_data.data}"/>
<mx:TextInput id="textInput"/>
<mx:Binding source="textInput.text" destination="_data.data"/>
</mx:Application>

The reason that _data is cast as an object is that in real code (unlike this simplified example) any one of a number of different types of objects could be fed in as the data to use. Next up is the implementation of SampleData that generates the "unconverted Bindable" error from above.

package com.example {
public class SampleData {
private var _data:Object;
[Bindable]
public function get data():Object {
return _data;
}
public function set data(data:Object):void {
_data = data;
}
}
}

This is where I get really confused about what the compiler and runtime are doing under the scenes. I've not looked at the "-keep" output to see if there is a good explanation. For the time being there are two solutions to the problem. The first one is to switch the order of the getter and setter in SampleData. The following change fixes the Bindable metadata and as such the error goes away:

package com.example {
public class SampleData {
private var _data:Object;
[Bindable]
public function set data(data:Object):void {
_data = data;
}
public function get data():Object {
return _data;
}

}
}

Subtle isn't it :) The [Bindable] documentation shows an example of the setter before the getter, it doesn't seem to emphasize that it can make a difference, which is why I suspect something else funky maybe going on. The other fix is to use more a strongly typed object reference. Using the SampleData class from above with the get before the set, this modified application will also cause the warning to disappear.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:example="com.example.*">
<mx:Script>
<![CDATA[
import com.example.SampleData;

[Bindable]
private var _data:SampleData = new SampleData();
]]>
</mx:Script>
<mx:Label text="{_data.data}"/>
<mx:TextInput id="textInput"/>
<mx:Binding source="textInput.text" destination="_data.data"/>
</mx:Application>

Again nice and subtle. If anyone has thoughts on why the order of the getter and setter might contribute to this warning, I'd love to hear them.

Tags: actionscript3 as3 bindable flex warning

June 30, 2006

Events in Flex 2

Today I got a familiar error while working with some event code:

TypeError: Error #1034: Type Coercion failed: cannot convert flash.events::Event@35f04c1 to com.neophi.events.CustomEvent.

The short answer is that this error results from failing to implement a clone() method on a custom event class. The long answer is that most of the time not implementing a clone() method won't hurt you, based on how you use your custom events, but you really should create one. In fact the Actionscript 3 (AS3) documentation is pretty clear about this:

When creating your own custom Event class, you must override the inherited Event.clone() method in order for it to duplicate the properties of your custom class.

This is a time that I wish AS3 had abstract methods.

Why I say you don't really need a clone() method is that it only becomes an issue if you relay an event. The follow code is an example of event relaying:

private function relay(customEvent:CustomEvent):void
{
    dispatchEvent(customEvent);
}

In this case an event listener redispatches the event that it got. When this happens the event framework behind the scenes calls the clone() method to create a new instance of the event. If you don't have a clone() method and the next listener in the chain is expecting an instance of CustomEvent, the error from above happens. If instead that same listener was only expecting an instance of the Event class, it would work, but any additional information contained in CustomEvent class would be lost, including the ability to cast it to an instance of CustomEvent.

In most cases the clone() method is simple to implement; just transfer any constructor arguments to a new instance of the class. Something like:

override public function clone():Event
{
    return new CustomEvent(_message);
}

Based on the type of event you will probably have more arguments including the all important type and often the other standard event variables for bubbling and cancelability.

While I'm on the subject of events I'll also talk about Event metadata or annotations that you can apply to classes. Often if you have an AS3 class that can emit custom events you will add metadata like this at the top:

package com.neophi {
    import com.neophi.events.CustomEvent;

    import flash.events.EventDispatcher;

    [Event(name="customChange", type="com.neophi.events.CustomEvent")]

    public class EventSource extends EventDispatcher {
        // class code removed 
    }
}

The thing to keep in mind is that depending on how this class will be used you probably don't need that metadata. If this AS3 class isn't used in MXML you can skip the metadata. The metadata is only used to inform the compiler how to translate an MXML event attribute into the appropriate code.

For example with the above class and metadata you could use it in MXML like this:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:neophi="com.neophi.*">
<mx:Script>
<![CDATA[
import com.neophi.events.CustomEvent;

private function customChanged(customEvent:CustomEvent):void
{
    output.text += "EventTest: " + customEvent + "\n";
}
]]>
</mx:Script>
<neophi:EventSource id="eventSource" customChange="customChanged(event);" />
<mx:Button label="Go" click="eventSource.source();"/>
<mx:TextArea id="output" width="100%" height="400"/>
</mx:Application>

Because cutomChange is defined in the metadata for the EventSource class, in MXML you can now assign a handler to that event. In this case the method customChanged will be called whenever our instance of EventSource dispatches that event.

The method the button is calling to cause eventSource to fire the event is:

public function source():void
{
    var customEvent:CustomEvent = new CustomEvent("Event Message");
    dispatchEvent(customEvent);
}

This code is part of the EventSource class listed above. A new instance of CustomEvent is created and then dispatched. At this point all listeners will get called.

If you tried instead to assign the customChange handler in MXML without the metadata in EventSource you would get a compile error like this:

Cannot resolve attribute 'customChange' for component type com.neophi.events.EventSource.

For events then the two rules of thumb are:

  1. always create a clone() method in any custom event class
  2. add event metadata if you will need to setup handlers for that event in MXML

Tags: actionscript3 as3 events flex programming