Binding read-only accessors in Flex
by Ruben
When I first tried to bind a read-only getter-function (see example-code below) the Flex SDK decided to warn me that "[Bindable] on read-only getter is unnecessary and will be ignored.".
private var _someProperty:String = "some value"; [Bindable] public function get someProperty ():String { return _someProperty; } // generates compile-time warning saying "[Bindable] on read-only getter is unnecessary and will be ignored." // note that there is no setter-functio
Furthmore, the LiveDocs said the following about the binding getter-only accessors:
If you define just a getter method, you create a read-only property that you can use as the source of a data-binding expression without inserting the [Bindable] metadata tag. This is similar to the way that you can use a variable, defined by using the const keyword, as the source for a data binding expression.
Bindable metadata tag (Adobe Flex LiveDocs)
This is, of course, complete and total bogus. Having a getter-method that is not accompanied by a corresponding setter-method does not necessarily mean that the method its returned value will not ever change, eliminating the option of using the const keyword.
So I had a quick read through the Flex LiveDocs and came up with the following solution:
// the inner internal: private var __someProperty:String = "some value"; // the public property (read-only): [Bindable(event='somePropertyChanged')] public function get someProperty ():String { return _someProperty; } // the internal (read/write): private function get _someProperty ():String { return __someProperty; } private function set _someProperty (value:String ):void { __someProperty = value; dispatchEvent(new Event("somePropertyChanged")); }
As you can see, where the someProperty public getter is made bindable, I used the event attribute of the [Bindable] metadata-tag. You can use the event attribute to assign an Event that will trigger the bindings with a certain member to be refreshed.
I assigned 'somePropertyChanged' as the Event that would refresh the bindings with someProperty.
I also turned the original internal ( _someProperty) into a getter/setter, and created an inner internal for the the internal getter/setter ( __someProperty, two underscores).
Now when the internal setter method ( _someProperty) is called, it automatically dispatches an Event typed 'somePropertyChanged', and thus indirectly triggers the bindings to the public accessor (someProperty) to be refreshed..
This is the cleanest and simplest way of doing this that I could come up with.
However, as far as the databinding mechanism is concerned, keep in mind that only explicitely resetting the value of a variable (for example myVar='new value') counts as change.
Executing a method of a certain variable (for instance myArray.push('new value')) does not count as change, and adjusting the value of an element of a variable (for instance an Array or Object) does not either.
I am currently still trying to find out what the best way is to handle databinding with Arrays or Objects, as soon as I figure it out I'll be sure to put together a follow-up post on this topic.
NOTE: Although, for the sake of clarity, I used the private attribute in the above code-examples, I strongly advise to use the protected in favor of private in almost any case.
This is because a subclass does have access to its superclass' protected properties, whereas it does not have access to its superclass' private properties.
Comments (read older or newer, or show 9 trackbacks)
Trackbacks:
Many thanks to everybody for providing me with feedback on this subject.
@Ely:
My problem lies with both with the error string and the way binding works with getter-only properties, not just the first.
Although I understand what you’re saying, I don’t entirely agree on that there is no way for the compiler to know where the return value of a getter comes from, because it can simply look right after the return keyword. Think about the way you can compose the text in a Label instance out of bindable properties.
So the compiler should be able to figure out (without all too much effort) when the return value of a getter is changed, provided that the return value consists of (a composition of) one or more properties that are all bindable.
In the case of one of the properties in the return-value not being bindable, then the compiler can throw me a warning that data-binding will not be able to detect changes made to the property (the getter)..
I think this a better approach mainly because calling a setter does not necessarily mean the return value of its accompanying getter will be different..
Second, I feel that the way it is right now, I have to go through alot of trouble (actually the trouble I wrote down in this post) to bind a property that is read-only for methods that are not in the same class..
@Josh:
I know that the LiveDocs don’t explicitely state that I should use the const keyword, but I do think that constants have nothing to do with binding a getter-only accessor. That was merely the point I was trying to make..
@Steve:
No when I talked about binding Arrays I did mean plain ol’ Arrays, not ArrayCollections. Even though I know that ArrayCollections supposedly (I don’t have much experience with them) are better for binding, I think it’s weird that (for example) .push() isn’t regarded as an actual change..
Hey Ruben,
Regarding array.push() not signalling a change, this seems pretty logical; Binding is a Flex-only feature, but Array is core to AS3, so it wouldn’t be right to add unnecessary overhead in to the Array class. The same goes for Objects. There is a lot of useful additional functionality in the collection classes – well worth more investigation :]
Cheers,
Neil
Well actually the thing with Array.push() wasn’t really something that surprised me, it was more of a note to the reader to keep in mind when making Arrays bindable.
Thanks though for pointing out the difference between core AS3 features and additional features of Flex, I hadn’t really thought about it that way before.
And I will definitely look into the collection classes some more.
Hi Ruben. In simple cases, yes, your get function just returns the value of a backing variable. And we could possibly rewrite the backing variable as well, so it generates the same change event when _it_ is set.
But getters can get arbitrarily complex. What if you’re returning a local variable from the function? Then the compiler has to do code flow analysis, and try and determine where the value of the local variable is coming from, which can get arbitrarily complex. What if all of those inputs to the return value aren’t bindable? Should the compiler make them bindable as well? What if they _are_ bindable, but under different event names? Should the compiler add watch functionality to your class to make sure that the event for the read only property is fired whenever one of the N arbitrary inputs to its value is changed?
Code generation, and code rewriting, is a tricky and debatably dangerous area to get into. As a general rule, we consider MXML to be a code generation langauge, that gives us free license to generate whatever code is necessary to fulfill the developer’s intent. But AS is a well defined imperative language, with an international standard behind it, and multiple compiler implementations. Even our current implementation of the [Bindable] tag…which relies on code rewriting and generation…gets lots of debate on the SDK team. So the idea of doing complex code flow analysis on a function, and generating lots of change watching infrastructure, goes beyond the level we typically feel comfortable with.
Having said that, when we decided to support [Bindable] on get/set properties, we debated supporting the simple case, where the get/set funciton is really just backed by a single private field.
Ely.
Point taken, I guess that it might be little too complex after all to add the binding-functionality for property-accessors I suggested. If it weren’t it would be nice to have though
And of course many thanks for your ever detailed feedback..