Archive for the 'Unit Testing' Category
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
The Presentation Model – post #1
I read Paul Williams' post about using the Presentation Model some time ago, but ran in to a couple of issues when trying to put it in to practise in a real-world project. Although I still use PMs, they are sometimes really helpers that are not quite following all the rules. David Deraedt also has a series of posts about using the Presentation Model and mentions issues he encountered (specifically using it alongside Cairngorm).
I won't go in to details about why I want to separate my code this way (the above links cover all of that), but I thought I'd take a fresh look and so I've gone back to basics by creating a really simple app based not on the demo app, but rather on a single bit of functionality from Martin Fowler's example here:
http://www.martinfowler.com/eaaDev/PresentationModel.html
All the app does is set the enabled status of the TextInput, based on the state of the CheckBox.
(Note: I've not bothered creating the Album DTO or storing the composer string, as they weren't strictly necessary and I wanted to keep the file as basic as possible. For the same reason I'm not dispatching custom events either, so for this example every binding will dispatch a PropertyChanged event.)
Hopefully my interpretation is correct - nearly all the comments in the code are copied/pasted directly from the Martin Fowler article so you can follow along. I've also set the initial value of the checkbox to true and used binding to provide the 'updating of the view' from the model.
If you've not come across the Presentation Model before I would suggest starting with this example by looking at the CheckBox in View.mxml, and then following through the process of what happens when a user clicks the CheckBox (this way the comments should be in some order).
Here is the example. Right-click for source code:
Presentation Model & 'Global' Variables
I just wanted to air one additional thought here, as I'm working on a modular project which doesn't use an established architectural framework (i.e. Cairngorm, Mate, PureMVC) but has been set up in such a way that common/shared objects are retrieved using something similar to Cairngorm's ModelLocator. Essentially any module can get hold of shared data using sytax similar to:
-
Application.application.getSharedThingy([identifier]);
Now I don't want to get in to a discussion about Singletons or global vars here - as a contractor working on someone elses project, some projects are architected to depend on globals and there isn't always the time or budget for a refactor - I think this is just a reality of the job. However, one great thing about Flex is that general views can be knocked up in MXML in minutes (anything you want to reuse is a candidate for a component or helper class). Therefore, as far as code-reuse goes for Flex views goes, I'm really not that bothered about having a reusable view.
The great thing about the Presentation Model is that it forces us to remove all non-view logic in to its own file. The resulting PM should be encapsulated, easy to unit test and have no knowledge of the view (the view does have knowledge of the PM). If I start sticking references to globals/Singletons in the PM, I break some of those rules. However, if I see my View as something disposable, why not access the singleton/global objects from within the view, and then 'inject' them in to the PM?
So for example, my PM could have a setApplicationPayload() method, and my view would get the payload object and set it on the model:
-
pMod.setApplicationPayload(Application.application.getDataObject([identifier]));
I'm not saying this is the way to go, but it does make working with globals a little better - it was just a thought and I'd be interested to hear your feedback on things you do to make using the Presentation Model setup easier.
2 comments
Creating Classes Dynamically By Name
On occasion you may want to create classes dynamically by name. To do this in Flex 3 you can use getDefinitionByName (it's previous incarnation was getClassByName). Here's a quick example:
-
//cc() is called upon creationComplete
-
private function cc():void
-
{
-
var obj:Object = createInstance("flash.display.Sprite");
-
}
-
-
public function createInstance(className:String):Object
-
{
-
var myClass:Class = getDefinitionByName(className) as Class;
-
var instance:Object = new myClass();
-
return instance;
-
}
The docs for getDefinitionByName say:
"Returns a reference to the class object of the class specified by the name parameter."
...so you may be wondering why in the above code we needed to specify the return value as a Class? This is because getDefinitionByName can also return a Function (e.g. 'flash.utils.getTimer' - a package level function that isn't in any class). As the return type can be either a Function or a Class the Flex team specified the return type to be Object and you are expected to perform a cast as necessary.
The above code closely mimics the example given in the docs, but in one way it is a bad example because everything will work fine for "flash.display.Sprite", but try to do the same thing with a custom class and you will probably end up with the following error:
ReferenceError: Error #1065: Variable [name of your class] is not defined.
The reason for the error is that you must have a reference to your class in your code - e.g. you need to create a variable and specify it's type like so:
-
private var forCompiler:SomeClass;
Without doing this your class will not be compiled in to the .swf at compile time - and there is reason behind this madness. The compiler only includes classes which are actually used (and not just imported). It does so in order to optimise the size of the .swf. So the need to declare a variable should not really be considered an oversight or bug, although it does feel hackish to declare a variable that you don't directly use.
Here is the code your would need to create a custom class called Person from a string (where Person resides in the top level package along with your default application).
-
//cc() is called upon creationComplete
-
private var forCompiler:Person; //REQUIRED! (but otherwise not used)
-
-
private function cc():void
-
{
-
var obj:Object = createInstance("Person");
-
}
-
-
public function createInstance(className:String):Object
-
{
-
var myClass:Class = getDefinitionByName(className) as Class;
-
var instance:Object = new myClass();
-
return instance;
-
}
You can declare properties for all possible classes that you intend to create but if you are not happy with doing that inside your main app Alex Harui has another suggestion (something I've not personally tried yet):
"The code for the class has to be in a SWF. It can be in the main SWF or
loaded via a module later. There is a compiler option (-includes) that
allows you to stuff other classes into a SWF without explicitly naming
them in your source code."
Passing in Parameters
Another thing you may find yourself wanting to do is to pass parameters to the constructor of your class (indeed, this is what got me on to the subject today). As far as I'm aware this isn't possible to do this in AS3 (and isn't part of the ECMA spec so it is unlikely to change - please leave a comment or email if you know otherwise ), so you may want to set up another method which can accept and set multiple parameters . If you pass in an array you may stumble across another problem - the original array you passed in becomes the first item of a new array ... okay, that probably sounds confusing so see here for a similar example and solution. In short, the solution is to use Function.apply, and you can see how I used it below:
-
private var forCompiler:Person;
-
-
private function cc():void
-
{
-
var obj:Object = createInstance("Person", ["bob", 30]);
-
}
-
-
public function createInstance(className:String, args:Array):Object
-
{
-
var myClass:Class = getDefinitionByName(className) as Class;
-
var instance:Object = new myClass();
-
instance.initArgs.apply(null, args);
-
return instance;
-
}
-
-
/*********************************************************************
-
//In person.initArgs()....
-
//Note: the constructor also accepts name & age, assigning them default values
-
//if not specified.
-
public function initArgs(name:String, age:uint):void
-
{
-
this.name = name;
-
this.age = age;
-
}
-
*********************************************************************/
Now, I'm not sure if this is the best approach, but it works, and with a deadline approaching that's good enough for now :]
6 comments