Binding read-only accessors in Flex
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-function
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.
June 23rd, 2008 | Quote
Hey Gareth, you’re welcome, glad you got it to work
June 23rd, 2008 | Quote
Thanks for posting this. I was wondering why my (supposed) bindings were not firing when I made my class [Bindable]. I added your method and everything now works exactly as expected. I also tried to change the setter to private (or protected) and I kept getting the 1000 error, but your method works perfectly.
June 14th, 2008 | Quote
Hey, nice solution! I’m going to try this.
February 28th, 2008 | Quote
thanks for your job !
October 11th, 2007 | Quote
@ruben… I should have mentionted that I was using Moxie. They must have resolved that issue in the new compiler.
-adam
August 24th, 2007 | Quote
Haha, alright then, well in that case nevermind what I said..
August 24th, 2007 | Quote
…not really different…
I guess this is what happens when you reply with all haste
August 24th, 2007 | Quote
@Adam: That was in fact what I initially tried to do. However there is/was a bug in Flex that shows a compile-time error when you try to use the setter. This is what one of the Adobe TechNotes says:
Common Flex 2 compiler errors and known issues
I’m going to write more about this in a new post as soon as I have the time..
@Daniel: Thank you, but how is that any different from what Adam already said?
August 24th, 2007 | Quote
Since ActionScript 3 allows you to use getters and setters with different scope, why not use a pulbic getter and a protected/private setter?
This code below works as you would expect with a read only property.
private var _foo:Object;
protected var fooChanged:Boolean = false;
[Bindable (event="fooChange")]
public function get foo():Object
{
return _foo;
}
protected function set foo(value:Object):void
{
if (value == _foo)
return;
_foo = value;
fooChanged = true;
dispatchEvent(new Event(”fooChange”));
// invalidate size/properties/displaylist
}
August 23rd, 2007 | Quote
Nice post… also, if you don’t care about the “read-only” thing showing up in ASDocs, you can just define a public getter and a private setter. See my post on this: http://adamflater.blogspot.com/2007/08/binding-with-getters-and-setting.html
-adam
————–
http://adamflater.blogspot.com
June 8th, 2007 | Quote
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..
June 8th, 2007 | Quote
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.
June 6th, 2007 | Quote
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.
June 5th, 2007 | Quote
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
June 2nd, 2007 | Quote
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..
June 2nd, 2007 | Quote
That is indeed pretty clever. However, isn’t the other side of the token that properties declared as bindable can also act as the destination of a binding change? If BindingUtils.bindProperty(this, “dataprovider”, model, “someProperty”); works, what happens if i change it to BindingUtils.bindProperty(model, “someProperty”, this, “dataprovider”);? I think the prerequisite for all bindable properties to be both read and write ensures compatibility with data-binding, considering that the use of strings to represent the properties don’t allow that level of strict-checking.
When you say databinding with arrays, don’t you mean like ArrayCollection?
June 1st, 2007 | Quote
“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…”
You are correct. The value of a getter may change at any time.
You’re responding to the following statement in the documentation:
“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.”
Notice that they say “similar”. As far as I can tell, they are not implying that the value of your getter will never change. I believe they mention constants because constants don’t need [Bindable] either.
June 1st, 2007 | Quote
Hi guys. what you’re bumping up against is a problem with the error string, not with the theory. There are two ways you can make a get/set property bindable.
first, you can dispatch your own custom event when the value (computed or variable backed) you would read if you accessed the get function changed. In this case you need to inform the compiler that the property change is signified by your own custom event using the [Bindable("eventName")] syntax.
Second, in simple cases, you can simply put [Bindable] on the property, with no event specified. In this case, you’re asking the compiler to make the get/set property bindable for you.
Now keep in mind that there’s no way for the compiler to actually tell if the value of a property get function would be different if called, short of doing an extensive code flow analysis of the get function, identifying all the inputs that might be affecting the value of the get function (i.e., member fields, statics, globals that are used in the get function and in any methods, global functions, closures, etc) it might call, and setting up watchers on every one of those to trigger the binding when any of them change. That’s prohibitively difficult, and expensive to do. So the compiler doesn’t try.
Instead when you put [Bindable] on a get/set property, the compiler makes it bindable with a little creative rewriting that allows the framework to watch the get function, and dispatch a change event when the get function is triggered. This means that automatic bindable properties don’t work when the get function is computed from multiple values, or when you change its value by setting a backing field, rather than using the set function.
It _also_ means that if you have no set function, we can pretty much guarantee that there’s no way automatically bindable get properties will be triggered. a read only propeerty is, to the compiler, completely opaque…at the moment, it has no idea where that value is coming from, and hence will never be able to ‘automatically’ trigger the binding.
So what the compiler is saying in this case is not that you can’t make read only properties bindable, but that specifically the [Bindable] metadata, in that form, with no event specified, will have no effect. the error message could probably use a little bit more clarity.
Ely Greenfield.
Adobe, Flex SDK.
June 1st, 2007 | Quote
Yea tell me about it man. What strikes me most is the theory they use to back up their choice of not allowing bindings on getter-only accessors; namely that you should use constants for read-only properties..
Well whatever, hopefully someone from the Flex enginering team bumps into this post and corrects this issue..
June 1st, 2007 | Quote
Cool work-around, but what a hassle to get around a ‘rule’ Flex is enforcing.