Creating read-only bindable properties in Flex.
There may be times when you use an implicit getter/setter to take advantage of data-binding, but you'd prefer the property to remain read-only to the outside world.
When trying to do this many people initially attempt the following:
[Bindable]
public function get someValue():int
{
return _someValue;
}
private function set someValue(value:int):void //<-- 'private' access modifier
{
_someValue = value;
}
Note the use of the 'private' access modifier on the setter. Unfortunately this can lead to an "Ambiguous Reference" error at compile time (if you try to set the value in your code) and is not the correct way to approach the issue.
Other people attempt to create the getter method only (marking it as 'bindable'), but don't create the corresponding setter method. However, this results in error at compile time if you try to set the value ("Attempted access of inaccessible property") or a warning at compile-time: "A bindable tag on a read-only getter is unnecessary and will be ignored". Don't bury your head in the sand and ignore this warning - your binding may initially appear to work because Flex retrieves all the values at application start-up, but subsequent changes to the value will not be reflected.
Recap - [Bindable] & What Happens Behind Scenes
Let's quickly go over what is happening behind the scenes when we use the [Bindable] meta tag.
You may have seen the Bindable tag written several different ways:
- [Bindable] //default
- [Bindable (event="someEventType")] //specifying an event type
- [Bindable ("someEventType")] //short-hand way of specifying an event type
When we use [Bindable] with no event argument specified, certain code is generated for us behind the scenes at compile-time. Some of that code ensures that an event called "propertyChange" gets dispatched for us when our bindable value has been set. Although we just write [Bindable], Flex interprets that as [Bindable (event="propertyChange")] and once our value has been set, "propertyChange" (represented by the static constant PROPERTY_CHANGE on the PropertyChangeEvent class) is also dispatched for us.
Essentially this is what is happening:
[Bindable (event="propertyChange")]
public function get someValue():int
{
return _someValue;
}
public function set someValue(value:int):void
{
if(value != _someValue)
{
_someValue = value;
dispatchEvent(new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE));
}
}
It's worth noting that the auto-generated code also performs a check, only updating the value if the value being set is different to the current value (see One Reason Why Your Flex Getter May Not Execute)
So, when you use [Bindable], Flex is guaranteeing that it will be responsible for dispatching the "propertyChange" event for you, and to do this it needs the corresponding setter method. If you don't provide a setter, it doesn't have a opportune point at which to dispatch the "propertyChange" event for you. It also throws up the warning "A bindable tag on a read-only getter is unnecessary and will be ignored". This error seems to suggest that you may be wrong in wanting a private setter, but really it's a poorly worded error-string and what it's trying to say is that if you're relying upon the default [Bindable] behaviour (ie where "propertyChange" being dispatched) you're going to need a public setter method too.
Declaring An Event
As soon as we specifically declare an alternative event type, we break the contract with Flex , and in doing so become fully responsible for dispatching our own event. Flex doesn't care where we dispatch the event from. It could be from inside a setter, but it doesn't have to be. If we don't dispatch any event at all Flex won't complain - our code simply won't update as expected. Let's take a lok at specifying our own eventType and dispatching it ourselves:
private var _someValue:int;
[Bindable (event="someValueChange")]
public function get someValue():int
{
return _someValue;
}
private function generateRandomValue ():void
{
_someValue = Math.round(Math.random() * 100);
dispatchEvent(new Event("someValueChange"));
}
Although we only have an implicit getter method (no corresponding setter), we no-longer get a compiler warning. Our implicit getter looks like a public property, but because it has no setter it can't be set. _someValue is private and so that can't be set from outside the class either.
In order for binding to work, we need to send out a notification when the value changes, so that anything bound to it can call the getter and retrieve the updated value - we fulfil this obligation by associating our getter with "someValueChange", and then dispatching an event with "someValueChanged" as our eventType when our value changes - the difference is, we're doing this from a method in our code (in this case, a private method) and not from a corresponding setter.
Again, the thing you must remember is that, when you specify [Bindable(event="") you *must* dispatch the event yourself, even if you are creating a public getter/setter. Whatsmore, it's recommended to avoid the default for performance reasons - if multiple properties all dispatch "propertyChange" when their value changes, imagine the extra work your app has to do to work out which property-change caused the event to be dispatched.
Note: In the last example, I used flash.events.Event with an event type of "someValueChange" but we could have dispatched any subclass of Event (as long as the event type was "someValueChange"). As always, the examples shown are kept small and concise and not intended to indicate best practice.
6 comments
6 Comments so far
Leave a reply
If you use a different access modifier for the get and set methods it screws up ASDoc generation. I’m pretty sure this is a documented bug in ASDocs.
The good news is that the “someValueChange” event does not have to fire in a set method for binding to work; it just has to fire. So you can dispatch it anywhere inside the component, and it will force binding to update, as appropriate.
Generally this is a more efficient to specify the event when setting up and binding.
Imagine you have a class with 5 bindable properties on it all using the default “propertyChange”.
Although FB will make sure this isn’t fired until a property is actually changed (again good practice to do in any of your setters), when the event is fired (for one bindable property), all items bound to any of the 5 properties will try to update (as they are all waiting for a “propertyChange” event).
Thanks, useful info on the ASDoc generation, I imagine knowing about that can save some headache :)
Yes as Jeffry points out, the event does not have to be fired from a set method ( see the last code example above, where it is fired from the private method ‘generateRandomValue’), and great advice from Tink (also already mentioned, but perhaps not clearly enough .. or maybe people are falling asleep before they reach the end of such an exciting post … can’t blame them ;-)
Merry Xmas.
Thanks Neil – useful and also adds to my understanding of the bindable event in the getter/setter working even if the event is not dispatched.
Thanks for this – I like the recap on getter/setter behaviour with regards to binding and is a neat pattern for supporting read-only bindable properties.
How would you bind to a function?
For example,
public function getValue():String {
return classA.id + “:” + classB.name + ” | ” + classC.department;
}
<mx:TextInput text=”{getValue()}” click=”trace(event.currentTarget.text)” />
Note: I’m doing the same thing in a getter (without a setter) and the value is not always current. I want the getter to “get” the latest value anytime that variable is accessed.