Flex data binding pitfalls

Flex data binding provides a powerful mechanism for reacting to data changes, but it can be exceptionally tricky to debug when it goes wrong. This post is an accumulation of advice gleaned from colleagues, blogs and painful personal experience into how to avoid some of the nastiest of binding woes.

The best place to start is to read Chapter 40 (Data Binding) of the Adobe Flex 3 developer guide; a great introduction to the topic. I’m taking this as assumed reading, so won’t be covering the many tips contained therein.

Let’s jump straight into the gory details…

1) Don’t use constants as Bindable event names.

public static const ASK_PRICE_CHANGE : String =
   "ASK_PRICE_CHANGE";

// DON'T DO THIS!!!!!

[Bindable(event=ASK_PRICE_CHANGE)]
public function get askPrice():Number
{
	return _askPrice;
}
public function set askPrice(value : Number) : void
{
	if (value != _askPrice) {
		_askPrice = value;
		dispatchEvent(new Event(ASK_PRICE_CHANGE));
	}
}

It’s a great idea in theory, but it doesn’t work. It looks like you’re using the constant here, but it’s just a literal string. If you change the constant value, the binding will not fire.

2) Don’t rely on the execution order of bindings.
You can’t rely on the order staying the same, even if it works for you now. A refactoring of your source file, or changes to the behaviour of future Flex compilers could cause your assumption to fail in future. Here’s a good example:

<mx:Panel id="foo"
   visible="{model.canViewFoo}"
   includeInLayout="{foo.visible}">
...
</mx:Panel>

This worked most of the time in Flex 3.0, but fails very frequently in Flex 3.3. The right way to do it is:

<mx:Panel id="foo"
   visible="{model.canViewFoo}"
   includeInLayout="{model.canViewFoo}">
...
</mx:Panel>

3) Be wary of swallowed exceptions
Exceptions thrown in binding expressions, or in functions called within binding expressions are silently captured by the binding framework.

4) [Bindable(“propertyChange”)] is not the same as [Bindable]
Even though behind the scenes, the default event name is “propertyChange”, if you explicitly refer to it in your [Bindable] tag, the compiler assumes you will be generating the event yourself. If other properties in the same class are bindable, their updates will not trigger a binding update on your property too. Worse yet, if you try and use dispatchEvent(new Event("propertyChange")) in your setter, you will end up with type coercion errors as the binding framework expects events with the name “propertyChange” to actually be of type PropertyChangeEvent. If you actually dispatch a PropertyChangeEvent event, all will be well, however you might as well of just labelled the property [Bindable] in the first place and let the code generator handle the donkey work.

5) Take extra care when using interfaces in binding expression
If you refer to your bindable class via an interface in a binding expression, you need to be aware of the following limitations:

  1. If the [Bindable] event name in your interface is different from the [Bindable] event name in your class, binding events will not fire.
  2. If you have both a getter and setter for a property and no [Bindable] tag in your interface, binding events will not fire.
  3. If you have only a getter for your property and no [Bindable] tag in your interface, binding events will fire. This is just a weird artifact and shouldn’t be relied upon in future.
  4. Be extra extra careful with nested interfaces.

If you wish to refer to your domain objects using interfaces in classes that use bindings, make sure the bindings metadata exactly matches. If you have multiple implements of an interface with different [Bindable] tags, don’t use the interface for binding expressions, as chances are, it won’t work. Instead, use Object and all your different binding implementations will work as expected.

Debugging bindings
When bindings do go wrong, what’s the easiest way to debug them? You can view the generated source, but don’t expect it to be especially enlightening. About the only useful thing you will glean from perusing the source, is to marvel at just how much of it there is.
A more constructive way to debug is to use the undocumented BindingManager class. This shows you when each of your bindings fire. Add the following line to your Application…

BindingDebugger.debugComponent(this);

…and include the following class…

package
{
import mx.binding.BindingManager;
import mx.binding.IBindingClient;
import mx.core.UIComponent;
import mx.core.mx_internal;

use namespace mx_internal;

// Kudos to Cathal Golden for coming up with this technique
public class BindingDebugger
{

private static const BINDINGS_PROPERTY : String =
“_bindingsByDestination”;

public static function debugComponent (
displayObject : Object, recursive : Boolean = true) : void
{
if (displayObject is IBindingClient)
{
var bindings : Object = displayObject[ BINDINGS_PROPERTY ];
for ( var binding : String in bindings )
{
BindingManager.debugBinding(binding);
}
}

if (recursive && displayObject is UIComponent)
{
var component : UIComponent = UIComponent( displayObject );
for (var i : int = 0; i < component.numChildren; i++) { debugComponent(component.getChildAt(i), true); } } } } } [/sourcecode] ...and you should see all your binding expressions traced out to the console with output like this:

Binding: destString = bid.text, srcFunc result = Bid $15.31
Binding: destString = calcSpread.text, srcFunc result = Calc Spread $2.79
Binding: destString = ask.text, srcFunc result = Ask $18.12

Next post, I’ll look at ways to optimize data binding when dealing with low latency streaming data.

Advertisements

Shuffle released on Android Market

It’s been a long time coming, but I finally managed to get Shuffle out the door and into users hands. I started work in 2007 as an entry in the Android Developer Challenge mostly for the draw of the prize pool, but also as a chance to learn what looked like a great mobile OS. After not winning I didn’t touch the code for several months. I did feel validated in my design decisions however when a month after posting the original screenshots OmniGroup published it’s first glimpse of OmniFocus on iPhone. Since they both follow the GTD mantra fairly closely, this isn’t too surprising.

I started working on Shuffle again in earnest a couple of months ago when I secured an Android Dev phone. Using the app on a real device was a huge leap forward. I made a ton of changes based on my experience using it every day.

I plan on stepping back a little now, but I don’t plan on abandoning the project entirely. One current idea is to setup a Shuffle GWT website running on the promising Java Google App Engine. I plan on kicking the App Engine tires soon, so should have a better idea then.