Flex data binding pitfalls: 10 common misuses and mistakes @EladElrom

Preview:

Citation preview

Flex data binding pitfalls: 10 common misuses and mistakes

@EladElrom

@EladElrom• Associate Dev Director @ Sigma Group• Senior Flash Engineer & Lead• Technical Writer• FlashAndTheCity Organizer• Adobe Community Professional

DataBinding - old topic yet still relevant

Data binding—the process of passing the data in one object to another object automatically—is one of the most used and useful features when building Flex and Adobe AIR applications. At the same time, however, data binding can slow application initialization and cause frustration when developers don't fully understand how the mechanism works. It is a good idea to make sure you are using it correctly and only when needed.

TOCFlex data binding pitfalls: common misuses and mistakes

1. Missing silent errors2. Trying to bind a class that does not include the

IPropertyChangeNotifier interface3. Using binding in place of direct assignment4. Forgetting to unbind and risking memory leaks5. Not changing the default propertyChange event constant6. Using the wrong bindable event name7. Assuming an execution order for binding8. Using binding in place of events9. Binding a class and its properties10. Using two-way data binding for unsupported properties

Lastly, Turbo Binding

Mistake #1: Missing silent errors

There are cases where the binding operation just does not seem to work, and you end up frustrated and unsure of what to do.

Exceptions and errors that are thrown by binding expressions, or in binding functions called within the binding framework, are silently captured. As a result, you will not see a runtime exception as you might expect in the debug version of Flash Player. Not only does the binding not work, but no errors are shown.

Missing Silent Errors

The code that implements the binding mechanism requires several conditions to be met before the binding will occur. The binding mechanism will swallow any errors to prevent runtime exceptions from being thrown at runtime. This is a good thing since you do not want to see these (possibly) unexpected errors in your application.

Why Are Errors being Silently Captured?

I have added an xml variable binding to a Label component. The code would have worked fine; however, I have set the xml variable to null during the pre-initialization of the component.

The event was dispatched at the beginning of the component initialization sequence, so the Label component was not set yet. The xml variable gets set to null, so there is no name property on the xml object. If you run this application, you'll notice that binding does not occur and the errors have been silently captured.

Binding Error Example

Although errors are captured silently, there are still things you can do to figure out what is going on. Debugging with the BindingManager.as and Binding.as code is not easy since the binding classes are not available to you unless you download the entire Flex SDK.

Instead, you can set a break point and drill down to the related binding objects to find out what went wrong. In this case, you would find that the value of the XML object is set to null, and that is why the binding has failed.

Debugging Binding

Take a look at the Binding.as and BindingManager.as classes. The code has many if and try… catch statements that ensure conditions are met for performing a valid binding. Here are some of the error cases that can be thrown when using binding:

* Error #1006: Call attempted on an object that is not a function. * Error #1009: Null has no properties. * Error #1010: Undefined has no properties. * Error #1055: Has no properties. * Error #1069: Property - not found on - and there is no default value

If any of these errors occur, the binding manager catches them silently and will not perform the binding.

Binding and BindingManager

Another approach, which is more intuitive, is to use the debugBinding method in the BindingManager class. You set the component and property you want to watch, and then you can see the errors that were fired and silently captured.

Debugging Binding

Runtime errors can occur normally in many scenarios.

1. a Label component is initialized that contains a data binding expression, the target property may not yet be set and execution of the binding expression will result in an Error #1009 – Null has no properties runtime exception.

2. Similarly, some data binding expressions are only valid when the user of your application performs an action such as selecting an item in a List. If your data binding expression references the selectedItem property of the List, it will be null until the user actually clicks an item in the List and selects it.

Null Errors Examples

In the example code I left the following line of code commented:

BindingManager.debugBinding("label.text");

Uncomment this line and run the application in debug mode; you can see the binding errors in the Console view.

Debugging Binding

Debugging Binding Console

Trying to bind a class that does not include the IPropertyChangeNotifier interface

Mistake #2

Classes that implement the IPropertyChangeNotifier marker interface must dispatch events for properties in the class, and any nested classes are publicly exposed as properties. As a result, you can find out when properties have changed in the class. For instance, take a look at the UIComponent class signature. UIComponent indeed implements dispatchPropertyChangeEvent, which will dispatch an event once properties have changed.

IPropertyChangeNotifier Marker

Now consider the following class that holds user information:

If you try to bind the text property of a Label to one of the properties of the UserInfo class, it will not work!

ValueObject

Because the code is trying to bind a class that does not implement IPropertyChangeNotifier, the binding mechanism will not work.

In this case, you'll see the following message in the Problems view (see Figure):

Reason

attach the [Bindable] tag to the class. This will enable all public properties of the class for data binding. The Flex compiler will generate a public getter and setter for you which will contain all of the code necessary to make data binding work. Alternatively, you can attach the [Bindable] tag to specific properties of the class if you don't want to enable all properties for data binding.

Solution

Data binding requires that the class to which you are binding implements the IPropertyChangeNotifier interface. Otherwise, the object is not bindable.

However, classes/properties or variables, such as primitive variables, that are not marked [Bindable] do not implement that interface. If it is your own class, all you have to do is add the [Bindable] metadata tag. If it's not your class you wish to bind with, or you want to add binding functionality during runtime you can use the ObjectProxy class. ObjectProxy wraps a non-bindable class and dispatches a PropertyChangeEvent when any of the properties of the class are changed, enabling objects in your application to listen for property changes.

ObjectProxy Class

Below is an example of using ObjectProxy. I create a new instance of ObjectProxy and

pass the object I want to watch, in this case UserInfo. I then add

an event listener and track changes to an item in UserInfo.

When using ObjectProxy is that in order to facilitate assignment notification, registered listeners will be invoked every time any property on the target object changes. That can potentially introduce a significant overhead, in fact in our example the onPropertyChange is called twice since two changes occurred.

ObjectProxy caveat

Mistake #3: Using binding in place of direct assignment

What’s wrong with the code below?

The code defines a TextInput control with a text property binding to the text private variable. It looks harmless enough, right?

I have seen these types of tags often in Flex applications. The Flex compiler generates code to allow the binding. You will find that although you do not need to bind the text String since it is a one-time assignment, the compiler still generates code to accommodate binding of the property.

Additionally, there are cases where you want to unbind after the assignment or remove the binding code to reduce overhead, but you will not be able to do so using the <mx:Binding> tag in MXML.

Answer

“-keep” generated mxmlc codeclass BindableProperty{

/* * generated bindable wrapper for property someTextInput (public) * - generated setter * - generated getter * - original public var 'someTextInput' moved to '_2040386569someTextInput' */

[Bindable(event="propertyChange")] public function get someTextInput():spark.components.TextInput { return this._2040386569someTextInput; }

public function set someTextInput(value:spark.components.TextInput):void { var oldValue:Object = this._2040386569someTextInput; if (oldValue !== value) { this._2040386569someTextInput = value; if (this.hasEventListener("propertyChange")) this.dispatchEvent(mx.events.PropertyChangeEvent.createUpdateEvent(this, "someTextInput", oldValue, value)); } }

/* * generated bindable wrapper for property someText (private) * - generated setter * - generated getter * - original private var 'someText' moved to '_1504842817someText' */

[Bindable(event="propertyChange")] private function get someText():String { return this._1504842817someText; }

private function set someText(value:String):void { var oldValue:Object = this._1504842817someText; if (oldValue !== value) { this._1504842817someText = value; if (this.hasEventListener("propertyChange")) this.dispatchEvent(mx.events.PropertyChangeEvent.createUpdateEvent(this, "someText", oldValue, value)); } }

}

package{[ExcludeClass]public class _TesterWatcherSetupUtil implements mx.binding.IWatcherSetupUtil2{ public function _TesterWatcherSetupUtil() { super(); }

public static function init(fbs:IFlexModuleFactory):void { import Tester; (Tester).watcherSetupUtil = new _TesterWatcherSetupUtil(); }

public function setup(target:Object, propertyGetter:Function, staticPropertyGetter:Function, bindings:Array, watchers:Array):void {

// writeWatcher id=0 shouldWriteSelf=true class=flex2.compiler.as3.binding.PropertyWatcher shouldWriteChildren=true watchers[0] = new mx.binding.PropertyWatcher("someText", { propertyChange: true }, // writeWatcherListeners id=0 size=1 [ bindings[0] ], propertyGetter);

// writeWatcherBottom id=0 shouldWriteSelf=true class=flex2.compiler.as3.binding.PropertyWatcher watchers[0].updateParent(target);

1. Avoid using binding to a private variables as much as possible.

2. Do not use data binding unless the property to which you are binding can or will change.

Rule of thumb

In the example I showed you previously you can use direct assignment to set the value:

<s:TextInput text="some text goes here" />

When you use direct assignment, you significantly reduce your overhead because the compiler does not create unnecessary binding code.

Direct Assignment

Forgetting to unbind and risking memory leaks

Mistake #4

You can use the <mx:Binding> tag in MXML or curly braces to easily implement binding; however, there is overhead associated with these methods.

Additionally, you cannot unbind properties using these techniques. If you are optimizing a high-performance application, you can use the BindingUtils class to bind your objects.

There are two ways to use the BindingUtils class:

* The bindProperty() method is a static method used to bind a public property. * The bindSetter() method is a static method used to bind a setter function.

BindingUtils

site = destinationhost = source

You set commitOnly to true when the handler is to be called only on committing change events; set it to false (the default) when the handler is to be called on both committing and non-committing change events.

A strong reference (the default) prevents the host from being garbage-collected; a weak reference does not.

public static function bindProperty( site:Object, prop:String, host:Object, chain:Object, commitOnly:Boolean = false, useWeakReference:Boolean = false):ChangeWatcher

bindProperty

The example includes a text input and a simple text component. When the TextInput control is preinitialized, preinitializeHandler() is called, which uses the bindProperty method.

bindProperty Example

Here is the bindSetter method signature:

public static function bindSetter(setter:Function, host:Object, chain:Object, commitOnly:Boolean = false, useWeakReference:Boolean = false):ChangeWatcher

The setter parameter specifies the setter method to invoke with an argument of the current value of chain when that value changes. Here again, host represents the source object, and chain represents the property name. The commitOnly and useWeakReference parameters work as with bindProperty.

bindSetter

Behind the scenes, the ChangeWatcher class is used in the BindingUtils class. It allows weak references, so when you set useWeakReference to true the host will automatically be picked up by the garbage collector, avoiding potential memory leaks.

Default (just like event listeners) is false!

bindSetter Example

When you don’t set the weak references to true, the ChangeWatcher object needs to be unwatched after you are finished with it. It is your responsibility to handle that task, which will ensure there are no memory leaks.

The best way to handle that is to assign the static method returned by bindProperty to a ChangeWatcher variable (since the static method returns a ChangeWatcher instance you can assign it to a variable). When you do not need to bind the object anymore you can use the unwatch method

Avoid Memory leak

The TextInput text property is binding to the Label text property, and once you type text in the TextInput, the data will be copied to the text property in the Label component. When you are ready to unbind, click Stop Binding.

This will unwatch the properties and set the object to null so it will be picked up during garbage collection.

Example

Not changing the default propertyChange event constant

Mistake #5

When you use the Bindable tag without adding an event setting, propertyChange is the default event type that will be dispatched.

[Bindable] = Bindable(event="propertyChange")

The compiler creates additional code for the setter and getter when you do not specify the event string.

It is recommended that you add your own name constant to avoid this extra overhead.

Default type

As you can see the mxmlc creates a generated setter which contains code that dispatches the PropertyChangeEvent.

If you don't change the default constant, every [Bindable] property will dispatch the propertyChange event and the listening code must interrogate the event to determine exactly which property changed. This is especially costly process if a class has a large number of [Bindable] properties.

Compiler creates a lot of code

Once you set the event name yourself, such as in the example below, the compiler just copies the code over.

When you change the default constant, the Flex compiler will NOT generate code and you are responsible for dispatching the event yourself. This allows you to optimize the listening code.

Do it yourself

Using the wrong bindable event name

Mistake #6

Using the wrong event name in the [Bindable] tag can cause your application to not bind your property, and you will not even know why! When you use the [Bindable] tag with a custom name, the example below looks like a good idea:

What’s wrong with this code?

The code above assigns a static property to the event name, and then uses the same assignment to dispatch the event. However, when the value changes, the binding does not appear to work. The reason is that the event name will be EVENT_CHANGED_CONST and not the value of the variable.

Correct code:

Answer

Assuming an execution order for binding* This one is a very common one!

Mistake #7

Assuming that binding occurs in a synchronous execution order. This can cause your application to generate warnings and not bind your property.

Events in ActionScript are executed in an asynchronous manner.

Common Mistake

The code above may work; however, it may not. It assumes that the Label text property was already set since the value of label.text in the button component is binding to the Label.text property. If you compile this application you'll also get a compile time warning.

Example

Compile time error

Below is another example. It assumes that the value of the first TextInput control is already set. This type of assignment also causes the compiler to generate all the code needed for binding. You have to decide if that code is needed or if a direct assignment (y=35) is a better choice.

Another Example

Using binding in place of events

Mistake #8

There are many cases where you can write your code easily without data binding and instead use events to assign a value.

You can set a value using the appropriate component lifecycle event or overriding component’s methods such as childrenCreated() or initializationComplete() to do the a value assignment.

Additionally, there are times when you can use ChangeWatcher to listen to changes in data, which is less costly. When using ChangeWatcher keep in mind that there are many cases where you can even avoid using the ChangeWatcher since you can manually get notification.

For instance, all collections in Flex have a collectionChange event broadcasted, which you can use to manually get change notification within the collection object.

Explanation

Example

It looks like pretty standard code for a Flex application. However, the type of binding illustrated here is not needed.

use direct assignment in an event handler:

Better way…

Binding a class and its properties

Mistake #9

Another common mistake is to set a class to be bindable and then make each property in the class bindable as well; for example:

Example

The [Bindable] tag is not needed for the CustomerID property because the class is already marked as bindable, which makes every property in the class bindable. This creates compile time warnings (see Figure) and writing the extra code wastes time. Unless you specify an Event name the tag is redundant and should be removed.

Compile time warnings

Using two-way data binding for unsupported properties

Mistake #10

Flex 4 supports two-way data binding. You can create the binding by adding @ in front of one of the curly braces, while leaving the other field unbound. For example, if you run the application below and type a value in one text field the value is mirrored in the other text field:

Example

Two-way data binding works in most cases; however, it does not work with style or effect properties. It also does not work with the arguments property for RemoteObject or the request property for HttpService, RemoteObject, or WebService.

Two-way databinding fails

TurboBinding

Q&A

@EladElrom

Recommended