A while back, a friend of mine and fellow Flex developer was consulting on a Flex 4 based application. He was having a troublesome issue where in some cases the skin state was not updating in his application when he called invalidateSkinState(). The problem was that when he called invalidateSkinState(), the getCurrentSkinState() was never called during the next commit properties phase (think validation during maturity, eee our whitepaper for more details). It wasn't consistent in the code and it only happened in a few specific cases.
He asked me if I had ever seen the issue where getCurrentSkinState() is skipped after calling invalidateSkinState(). At that time I had never seen a problem with the Skin state not updating, so I didn't have much guidance for him. He found a solution which involved making sure to call both invalidateSkinState() and invalidateProperties() at the same time, even though invalidateSkinState() should also invalidate properties for you.
About three weeks later, I stumbled upon the same issue while working on an application. Inexplicably, one of my components started ignoring invalidateSkinState() and the skin wouldn't change. After setting some breakpoints and screaming at the code for a while I finally figured out what the cause was. I had mistakenly used invalidateSkinState() within the commitProperties() after calling super.commitProperties():
/* don't do this... */
override protected function commitProperties():void {
// call super first
super.commitProperties();
... calculate data, set the flag if the skin needs to change ...
// set the skin state
if(_flag) {
_stateIWant = "stateName";
invalidateSkinState();
}
}
I am simplifying the code dramatically, but what I was trying to do was during the commit phase I was calculating a lot of data set by a dataProvider. If the data had certain properties I needed to change the state of my skin, and that was why I was calling invalidateSkinState() post-data calculation. The core issue was that during the setting of the dataProvider, I had no idea if my skin had to change or not because the new data had to be processed before I could determine what state I needed to be in. At that point, I still wanted to delay the data processing until the commit phase, so I deferred the calculation until later and then post-calculation I could set the proper state.
Looking back, this was clearly a bad thing to do but at the time it seemed like a reasonable request. The problem with this call is around order of operation. Let's look at the order of operation within the UI component:
-
Invalidation
- invalidateProperties() is called
- invalidateProperties() sets invalidatePropertiesFlag = true
- The component registers with LayoutManager for the next validation pass
-
Validation
- LayoutManager begins the validation pass (started by ENTER_FRAME / RENDER event)
- LayoutManager calls validateProperties() on the invalid component
- validateProperties() determines if invalidatePropertiesFlag is set to true
- if true, validateProperties() calls overridden commitProperties()
- overridden method first calls super.commitProperties()
- super.commitProperties() determines if the skinStateIsDirty has been marked true (set via invalidateSkinState())
- if skinStateIsDirty is true it calls getCurrentSkinState() to get the state name
- super is complete and returns to the overridden method
- we process the data, determine the skin needs to change
- we call invalidateSkinState()
- invalidateSkinState() sets skinStateIsDirty to true and calls invalidateProperties()
- invalidateProperties checks to see if invalidatePropertiesFlag is already set to true
- invalidatePropertiesFlag is set to true (we are still validating at this point), so invalidateProperties() does not re-start the invalidation process
- commitProperties is complete, and returns to validateProperties(), remember we are synchronous at this point
- invalidatePropertiesFlag is set back to false
- validation process is complete
-
Re-Invalidation
- At some point post the last validation pass, we call invalidateSkinState()
- invalidateSkinState() checks skinStateIsDirty, which is still set to true so invalidateProperties() is not called
At there lies the crux of the issue. The problem is that when we call invalidateSkinState() it still thinks the skin flag is dirty, meaning we are already waiting for a validation pass and therefore we don't restart the invalidation process. This means that our skin flags are now out of sync and we get odd behavior until the next full invalidation process goes through. Of course we may keep re-entering into the same conflict over and over depending on how circular our logic is in the code.
The way to fix this is to move our logic BEFORE the super call:
/* acceptable fix */
override protected function commitProperties():void {
// do our code first
if(_flag) {
_stateIWant = "stateName";
invalidateSkinState();
}
// then call super
super.commitProperties();
}
This allows the invalidateSkinState() method to set the skinStateIsDirty flag to true before the super is called and will prevent the issue.
Finally, in 99% of the cases you won't need to call invalidateSkinState() within commitProperties(). You will more then likely want to call invalidateProperties() and invalidateSkinState() at the same time. But remember, you will still need to do all your skin state flag calculations BEFORE calling super so that commitProperties() and getCurrentSkinState() process the flags correctly.

Great analysis and writeup!
Thanks for this. I've never been clear on the right time to make the super.commitProperties() call and as a result, have not been consistent. You've just given me excellent reason to change my habit and *always* make that call at the end of my override.
THANK YOU
Really good explanation post... your not alone!
I came across this very issue just yesterday would you believe, but due to project deadline I never had a chance to resolve it (although I was very close to trying to perform my custom commitProperties logic before calling super.commitProperties() to see if that fixed the issue.
I saw another guide [1] on the adobe site explaining how create advanced Spark components which I think contains information related to the problem you identified above (digging in to the source of the SkinnableComponent's commitProperties/invalidateSkinState also helped me).
Anyway, after attempting to refactor a component from using public [Bindable] properties to getter/setters & using the invalidation/commit technique to make use of the framework component lifecycle (as i feel you can always rely on this technique, I don't like overuse of binding, and the framework methods are more efficient/reliable anyway).
Thanks again for your great explanation.
I'm loving using spark compared to halo (well done Adobe), and its no surprise there are a few gotchas when it comes to using the new skinnable component framework.
Good Luck,
Barry
[1] http://help.adobe.com/en_US/flex/using/WS460ee381960520ad-2811830c121e91...
btw... my issue was that I needed to process some dependant properties that are relied upon to configure the skin state first (as super.commitProperties will call getCurrentSkinState).
Post new comment