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. TOC Flex data binding pitfalls: common misuses and mistakes 1. Missing silent errors 2. Trying to bind a class that does not include the IPropertyChangeNotifier interface 3. Using binding in place of direct assignment 4. Forgetting to unbind and risking memory leaks 5. Not changing the default propertyChange event constant 6. Using the wrong bindable event name 7. Assuming an execution order for binding 8. Using binding in place of events 9. Binding a class and its properties 10. Using two-way data binding for unsupported properties Lastly, Turbo Binding Mistake #1: Missing silent errors 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. Why Are Errors being Silently Captured? 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. Binding Error Example 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 preinitialization 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. Debugging Binding 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. Binding and BindingManager 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. Debugging Binding 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. Null Errors Examples Runtime errors can occur normally in many scenarios. 1. 2. 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. 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. Debugging Binding 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 Console Mistake #2 Trying to bind a class that does not include the IPropertyChangeNotifier interface IPropertyChangeNotifier Marker 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. ValueObject 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! Reason 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): Solution 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. ObjectProxy Class 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 nonbindable 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. 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. ObjectProxy caveat 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. Mistake #3: Using binding in place of direct assignment What’s wrong with the code below? Answer 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. “-keep” generated mxmlc code class 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); Rule of thumb 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. Direct Assignment 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. Mistake #4 Forgetting to unbind and risking memory leaks BindingUtils 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. bindProperty public static function bindProperty( site:Object, prop:String, host:Object, chain:Object, commitOnly:Boolean = false, useWeakReference:Boolean = false):ChangeWatcher site = destination host = 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. bindProperty Example 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. bindSetter 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 Example 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! Avoid Memory leak 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 Example 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. Mistake #5 Not changing the default propertyChange event constant Default type 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. Compiler creates a lot of code 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. Do it yourself 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. Mistake #6 Using the wrong bindable event name What’s wrong with this code? 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: Answer 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: Mistake #7 Assuming an execution order for binding * This one is a very common one! Common Mistake 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. Example 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. Compile time error Another Example 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. Mistake #8 Using binding in place of events Explanation 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. Example It looks like pretty standard code for a Flex application. However, the type of binding illustrated here is not needed. Better way… use direct assignment in an event handler: Mistake #9 Binding a class and its properties Example Another common mistake is to set a class to be bindable and then make each property in the class bindable as well; for example: Compile time warnings 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. Mistake #10 Using two-way data binding for unsupported properties Example 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: Two-way databinding fails 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. TurboBinding Q&A @EladElrom