Saturday, March 27, 2010

An AdvancedDataGrid with a built-in Column Selector

The AdvancedDataGrid reminds me of Excel but the fact is that it is no where close to what Excel does. The AdvancedDataGrid is a complex component with many different classes (AdvancedDataGrid, AdvancedDataGridColumn, AdvancedDataGridHeaderRenderer and many other) working together to render the grid. Not much effort has been put into advancing the AdvancedDataGrid and make it behave a little bit like Excel. Here is my feeble effort to make it a little bit more advanced.

I have seen the need to show/hide column of a grid in a few projects. The first time, I put a PopupDropdown with a list of columns having a checkbox next to it. The second time we had a similar need and one of my colleague challenged me with a built-in column selector inside the AdvancedDataGrid. In the end, what he proposed is what he got. The screenshot below shows the end result.



Click here for a demo
View source: Right click demo, or download from here

[Incomplete:explanation coming soon]

Labels: , ,

Friday, March 26, 2010

ComboBox memory leak

A ComboBox is a very commonly used control. It looks very harmless but can be very dangerous as it leaks memory.

A Flex bug has been filed and here is the link. But Adobe is lazy and they will do nothing other than sit over the bug.

You can download the source code from here for the below example if you would like to profile and see the memory leak in the profiler.

The demo contains 2 custom components.
CustomComponent1: is a very simple component which contains a TextInput and CustomComponent2.
CustomComponent2: is also a very simple component. It contains a dropdown.
A button in the application removes the CustomComponent1. CustomComponent1 being a hello world type component, it should be garbage collected once it is removed from the displayList. But it does not. Check the image below.


The above screenshot is taken from the profiler. CustomComponent2 leaks and the references have been expanded. One of object referencing it is mx.controls.List. But List is really not the child of CustomComponent2 than why would it reference it? The document is CustomComponent2 hence all children and grand children of CustomComponent2 have the document property set to CustomComponent2 unless there is another document that contains the grand children. The List's document property is CustomComponent2. Finally, the List is being referenced by SystemManager as highlighted in red.

But why is the List referenced by the SystemManager? A ComboBox contains a dropdown which is really a List. The dropdown though referenced by the ComboBox is really not the child of the ComboBox. It is a Popup. Check ComboBox.as and you will see a lot of PopupManager.addPopup(_dropdown) lines. It is made to look as if it is dropping out of the ComboBox. A popup's parent is always a systemManager. That explains the relation between ComboBox -> dropdown (List) -> SystemManager.

Check this comment in the ComboBox.as ...

private function removedFromStageHandler(event:Event):void
{
// Ensure we've unregistered ourselves from PopupManager, else
// we'll be leaked.
destroyDropdown();
}

... despite knowing about the potential leak, the leak still exists.

Solution:
Extend ComboBox and add this method ...


//add event listender in the constructor
addEventListener(Event.REMOVED_FROM_STAGE, this_onRemovedFromStage);

...
...

protected function this_onRemovedFromStage(evt:Event):void {
if (mx_internal::hasDropdown()) {
var dd:ListBase = dropdown;
if (dd && dd.parent) {
dd.parent.removeChild(dd);
}
}
}


Now imagine an application which contains ...
CustomComponent1 which contains
    |____CustomComponent2 which contains
        |____CustomComponent3 which contains
            |____ComboBox which references
                |____dropdown whose parent is
                    |____SystemManager

The dropdown leaks hence the entire chain of components will leak. This bug has been reported to Adobe but I am not sure why they haven't fixed it.

Labels: , ,

Wednesday, March 24, 2010

Flex Builder: What is my current workspace?

I work on a project that has multiple branches. Sometimes I am working on branch1 and sometimes on branch2. I would like Flex/Flash Builder to display the branch I am working on. A quick search on the net and I found the solution.

Open the FlexBuilder.ini file. Add this to the first line "-showlocation". This is what my FlexBuilder.ini file looks like...

-showlocation
-vmargs
-Xms128m
-Xmx512m
-XX:MaxPermSize=256m
-XX:PermSize=64m
-Djava.net.preferIPv4Stack=true

This what my FlexBuilder title looked like before adding showLocation

...and this what it looks like after adding showLocation.


The FlexBuilder.ini file should be in your FB install folder.

Labels:

Tuesday, March 23, 2010

TextInput as Label

I have often seen the need to make a TextInput behave like a Label. This need generally comes from a form which is in editable/read only mode.

Here is a TextInput mxml that shows you what properties and styles need to change to make the TextInput behave like a Text or Label.


<com:TextInput text="prashant"
backgroundAlpha="0" borderStyle="none"
editable="false" focusThickness="0"/>

I remember seeing an example posted by someone where a Label was included inside the TextInput and then with the help of some black magic, the Label is displayed and UITextField inside the TextInput is made invisible. I liked my solution more for the simple fact that it does not have to add any extra uicomponents and works on the same object which is the UITextField.

Click here for a demo
View source: Right click demo, or download from here

The demo extends the TextInput class and adds a new property called "useAsLabel".

Labels: , ,

Sunday, March 21, 2010

Removing mxml/inline event listeners

If you are thinking that why does this topic deserve a blog than I highly encourage you to read further and I promise you a little bit of Flex enlightenment.

How do you remove the click event listeners of myButton below...

<mx:Button id="myButton" click="clickHandler(event)">

if your answer is...

myButton.removeEventListener(MouseEvent.CLICK, clickHandler, false);

than you are in for a bit of surprise because this will not remove the event listener.

UIComponentDescriptors:
The Flex compiler generates actionscript code from MXML. It generates UIComponentDescriptors for mxml components which are considered documents. In one line, a document is a class that ends in ".mxml". The compiler generates AS3 code for ".mxml" files. The descriptors are bunch of nested objects of type Function, UIComponentDescriptor, Object and Class. A UIComponentDescriptor is used by actionscript at runtime to create the mxml component and its children and grand children. They also contain information regarding the various mxml/inline event listeners e.g. the click event listener for the myButton above. In this blog, we will focus on as3-generated event listeners.

Here is what an mxml event listener converted to as3-generated looks like. The code below is partial code copied from an as3-generated file.
Code 1:

new mx.core.UIComponentDescriptor({
type: mx.controls.Button
,
id: "myButton"
,
events: {
click: "__myButton_click"
}
,
propertiesFactory: function():Object { return {
label: "My Button"
}}
})

Explanation:
I have a button called "myButton" and on click of that button I want to invoke a method called "__myButton_click". Hold it, my event listener was "clickHandler" and not "__myButton_click". The compiler created a "PUBLIC" wrapper function and calls "clickHandler" from that wrapper function. This is what the wrapper function looks like.
Code 2:

public function __myButton_click(event:flash.events.MouseEvent):void
{
clickHandler()
}

The question is, why does the compiler do this. My educated guess: Many times, developers write actionscript code in the mxml/inline event e.g...

<mx:Button id="myButton" click="myIntVar = 0; myBooleanVar = false">

To handle this situation, the Flex compiler wraps all event listener code in a generated function. This design pattern takes care of many different types of event listeners (inline actionscript code, anonyomous function as an event listener, a function that already exists in the document, a listener function that is private etc).

Now you see why the removeEventListener() will not work. So the question is, how do I stop listening to an inline event listener. Let's take a look at this demo.

Click here for a demo
View source: Right click demo, or download from here.


Some details to know about this application...

  • In the above example, Application, EmployeeAddress.mxml, EmployeeBasicDetails.mxml and EmployeeDetails.mxml are documents and their isDocument property will be true.
  • The EmployeeXXX.mxml contain TextInput controls which have event listeners for valueCommit. In the eventListeners we invoke a static method in the Util class to dispatch a custom event and also to update the log.
  • EmployeeDetails contains EmployeeAddress and EmployeeBasicDetails and some other mx controls. It also declares an event "eventFromComponent" which the application listens to and shows in the log TextArea.


How to use the demo ...
1. Tab through all the TextInput's and you will see 2 logs for every tabbing. One log from the Util class when dispatching the event and one from the Application event listener after listening to the event. Both will have the same counter number.
2. Click on the button to remove all inline event listeners.
3. Tab through all the TextInput's again. No logs will be created except for Address textinput (which I will explain later).
4. Check the logs below. Upto log #6 were generated in step 1. Log #7 was generated in step 3.
This is what the logs look like...

#7:dispatching 'eventFromComponent'
#6:listened 'eventFromComponent'
#6:dispatching 'eventFromComponent'
#5:listened 'eventFromComponent'
#5:dispatching 'eventFromComponent'
#4:listened 'eventFromComponent'
#4:dispatching 'eventFromComponent'
#3:listened 'eventFromComponent'
#3:dispatching 'eventFromComponent'
#2:listened 'eventFromComponent'
#2:dispatching 'eventFromComponent'
#1:listened 'eventFromComponent'
#1:dispatching 'eventFromComponent'

Removing event listener code is in the Util class. The entry method to remove the inline event listeners is...
"disposeInlineEventListeners(disposeUIComponent:UIComponent, disposeChildDocumentEventListeners:Boolean):void {"

I will skip a detailed explanation of the component descriptors but here are some bullet points.

  • A component descriptor can contain child component descriptors.
  • It contains the inline event listener related information and that is what my functions use to remove the event listeners.
  • An UIComponent has a property called "descriptor". Container's also have a property called "childDescriptors" and "mx_internal::_documentDescriptor".
  • A Container has a method called "createComponentsFromDescriptors" which utilizes the "mx_internal::_documentDescriptor" to create its children.
  • The _documentDescriptor is mutable and during processing and children creation, the _documentDescriptor is modified hence it is not very valuable post children creation.
  • The inline event listener information is scattered in descriptor/childDescriptors of the document and its children.
  • Hence we use the "descriptor/childDescriptors" of the document and of the children to remove the inline event listeners.
  • A UIComponent has a property called "isDocument" which determines if a component is a document or not.


In the above demo, click in the Address TextInput and than tab out. An event is dispatched and the logs show that. Why are we left with this event listener?
Answer, Look at "Code 1:" above. The componentDescriptor contains an "id" property. My remove inline event listener code uses this property to get a handle to the object to remove the event listener. If an "id" is not provided in the mxml control declaration than the descriptor.id will be null and the remove event listener will fail. The Address TextInput does not have an id hence the remove event listener fails.
Note: In some cases, I have noticed that the compiler provided a generated id. I am not sure under what circumstances.

If you would like to examine the as3 generated files than you can get it from here.

I will be writing soon on as3-generated files for mxml components. Do not hesitate to ask a question if you have any doubts or questions.

Labels: , , ,

Saturday, March 20, 2010

Unbind mxml (curly brace) bindings in Flex

Flex developers love the inline curly brace bindings. We just cannot do without it but along with that comes the pain of performance and memory leaks. I will not talk about performance in this blog but I will show you how you can remove these bindings.

The first question is, why should I remove the bindings? Why doesn't this happen automatically? Lets understand this simple component and its references...

//MyComponent.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300">
<mx:Script>
<![CDATA[
[Bindable] private var _firstName:String;
]]>
</mx:Script>
<mx:TextInput id="myTextInput" text="{_firstName}"/>
</mx:HBox>

What happens if another object someObj is listening to the myTextInput keyDown (or any other) event. Lets understand the references...
- MyComponent has a reference on myTextInput because it is a child of MyComponent.
- myTextInput has a reference on MyComponent because of the parent property and text binding.
- someObj has a reference on myTextInput because it is listening to the keyDown event (assuming it was not a weak event listener).

When you expect the MyComponent to get garbage collected, it won't because someObj references myTextInput and myTextInput references MyComponent. MyComponent will be garbage collected only if myTextInput's references to MyComponent are released. To make this happen, we will have to call MyComponent.removeAllChild() and remove all bindings. removeAllChild() is straight forward, lets look at bindings.

The right way to bind is through ActionScript. Keep a handle to the ChangeWatcher returned by BindingUtils.bindSetter() and BindingUtils.bindProperty(). When needed, just call changeWatcher.unwatch() and remove the binding. But there is no concept of changeWatchers in mxml bindings. So how do we release them?

Click here for a demo
View source: Right click demo, or download from here.

As you can see, once the binding is removed, the textInputs stop responding to the change. Before you scroll down to the complete code, lets understand what are these bindings, how do they look like and how to remove them.

Here is what an as3-generate binding looks like...

var result:Array = [];
var binding:Binding;

binding = new mx.binding.Binding(this,
function():String
{
var result:* = (int1);
var stringResult:String = (result == undefined ? null : String(result));
return stringResult;
},
function(_sourceFunctionReturnValue:String):void
{
_testRemoveCurlyBraceBindings_Label2.text = _sourceFunctionReturnValue;
},
"_testRemoveCurlyBraceBindings_Label2.text"
);
result[0] = binding;

...
...
...
mx_internal::_bindings = mx_internal::_bindings.concat(bindings);
mx_internal::_watchers = mx_internal::_watchers.concat(watchers);

The Flex compiler generates this type of binding code in the as3-generated file and keeps adding it to the result array.

Every generated actionscript (from mxml component) also contains mx_internal::_bindings, mx_internal::_bindingsByDestination, mx_internal::_bindingsBeginWithWord and mx_internal::_watchers properties. I know this is enough information for you to open Flex/Flash Builder and start playing with the mx_internals. But you can save some time by reading a few lines more. These mx_internal variables contain the mxml/curly brace binding details and all you need to do is get rid of them. Here is the function I wrote that releases the mxml bindings.


private function disposeAllCurlyBraceBindings():void {
var i:int = 0;
var len:int = 0;

//tear bindings
var myBindings:Array;
var bindDestinations:Object;
var bindBeginWithWord:Object;
try {
myBindings = mx_internal::_bindings;
bindDestinations = mx_internal::_bindingsByDestination;
bindBeginWithWord = mx_internal::_bindingsBeginWithWord;
mx_internal::_bindings = null;
mx_internal::_bindingsByDestination = null;
mx_internal::_bindingsBeginWithWord = null;
} catch (err:Error) {
trace("TEARDOWNVIEW_ERROR processing bindings");
} finally {
if (myBindings) {
len = myBindings.length;
for (i = 0; i < len; i++) {
var binding:Object = myBindings.pop();

if (binding) {
binding.mx_internal::isExecuting = false;
binding.mx_internal::isEnabled = false;
binding.mx_internal::isHandlingEvent = false;
binding.mx_internal::destFunc = null;
binding.mx_internal::srcFunc = null;
binding.mx_internal::destString = null;
binding.mx_internal::disabledRequests = null;
binding.twoWayCounterpart = null;
binding.uiComponentWatcher = -1;
binding.mx_internal::document = null;
}
}
}
deleteAllDynamicProperties(bindDestinations);
deleteAllDynamicProperties(bindBeginWithWord);
}

//tear watchers
var myWatchers:Array;
try {
myWatchers = mx_internal::_watchers;
mx_internal::_watchers = null;
} catch (err:Error) {
trace("TEARDOWNVIEW_ERROR processing watchers");
} finally {
if (myWatchers) {
len = myWatchers.length;
for (i = 0; i < len; i++) {
var watcher:Object = myWatchers.pop();

if (watcher) {
if (watcher.hasOwnProperty("value")) {
watcher["value"] = null;
}
}
}
}
}
}

private function deleteAllDynamicProperties(obj:Object):void {
if (!obj) {
return;
}

for (var str:String in obj) {
delete obj[str];
}
}



Explanation:
Get a handle to the mx_internal binding properties. Do this in a try/catch block because some ui components like Repeater and ActionScript components do not contain these properties. If they exist than set their internal properties to null. Also set the mx_internal bindings to null.


I am working on a project where almost all the custom components leak. Every component I profiled had 100-350 references. It was alomost impossible to analyze so many references, cross references and circular references. I have written a generic method that tears down the view (as much as possible) and removes most of the references. After calling this method, my reference count came down to 10-20. This helped me immensely in the reference analysis and fixing the memory leaks.

Also read my blog on removing mxml/inline event listeners and how to get rid of them.

Labels: , ,

Flex Tree control bug: selectedItem loses value and nothing is shown highlighted

One of the project I am working on uses a Tree control. Clicking on an item in the tree control selects that item and shows it highlighted but not in the application I was working on. Read about my 18 hour journey to fix this problem...

Clicking on a tree item would select the item but the selection would be lost in a few milliseconds.
A Tree extends List which extends ListBase and between them, there are about 15000 lines of code. Everything looked good, breakpoints did not reveal anything interesting. FlexSpy showed that the tree.selectedItem was null and it shouldn't have been because an item was selected by clicking on it. Breakpoint in the ListBase.selectedItem showed nothing unusual but soon I learnt that the ListBase class sets the private variable _selectedItem directly and that too in about 20+ places. Breakpoints in these 20+ places did not reveal anything and in debug mode I could not reproduce the bug. One of my colleague terms it as "Heisen Bug". A HeisenBug is a bug whose presence is affected by the act of observing it.

You cannot do much with the Flex framework code other than set breakpoints. You cannot add trace statements etc. So I decided to create my own Flex framework project and add it in the library path of my application project. I will write more about this in a different blog. I also have a script that adds trace statements in all the methods of all the classes inside a folder and its sub-folder(s). This script is also coming soon on one of my blog. The Tree/ List/ ListBase methods were modified to add the trace statements using the script I mentioned earlier. The traces were very helpful because in every method call, I would trace the selectedItem. I narrowed down the method where the selectedItem was an object and then suddenly became null. What was really happening?

The trace statements helped me narrow the problem down to a method called "collectionChangeHandler" which was being called because the dataProvider was changing. I was confident that the application was not changing the dataProvider. Then who was and why? The Tree.commitProperties() was changing the dataProvider. I am reproducing part of Tree.commitProperties() code below ...


if (dataProviderChanged || showRootChanged || openItemsChanged)
{
var tmpCollection:ICollectionView;
//reset flags

dataProviderChanged = false;
showRootChanged = false;

//we always reset the open and selected items on a dataprovider assignment
if (!openItemsChanged)
_openItems = {};

// are we swallowing the root?
if (_rootModel && !_showRoot && _hasRoot)
{
var rootItem:* = _rootModel.createCursor().current;
if (rootItem != null &&
_dataDescriptor.isBranch(rootItem, _rootModel) &&
_dataDescriptor.hasChildren(rootItem, _rootModel))
{
// then get rootItem children
tmpCollection = getChildren(rootItem, _rootModel);
}
}

// at this point _rootModel may be null so we dont need to continue
if (_rootModel)
{
//wrap userdata in a TreeCollection and pass that collection to the List
super.dataProvider = wrappedCollection = (_dataDescriptor is ITreeDataDescriptor2) ?
ITreeDataDescriptor2(_dataDescriptor).getHierarchicalCollectionAdaptor(tmpCollection != null ? tmpCollection : _rootModel, itemToUID, _openItems) :
new HierarchicalCollectionView(tmpCollection != null ? tmpCollection : _rootModel, _dataDescriptor, itemToUID, _openItems);
...
...
...
}


The condition check "if (dataProviderChanged || showRootChanged || openItemsChanged)" on line 1 passed because the "openItemsChanged" variable was true. This creates a new collection and assigns it to the dataProvider. This flag is set to true if you have set the openItems property of the Tree. All dirty flags have to be set to false at some point BUT THIS ONE WAS NOT and in every Tree commitProperties() call, a new collection was created and assigned to dataProvider which in turn was setting the selectedItem to null. Searching flex bugs revealed that I was not the first guy to have come across this bug.

https://bugs.adobe.com/jira/browse/SDK-23388
I had searched flex bugs but this bug did not show up in the search results because it didnt have the TREE keyword in the title.

Somebody else had a similar dataProvider problem but I am guessing he didnt get to the root of the problem.
http://butterfliesandbugs.wordpress.com/2007/11/08/workaround-for-selectedindex-on-tree-not-showing-up/

My solution ...
- Extend the Tree class and create a new class MyTree.
- override the set openItems method and in the method open the items individually one by one using expandItem() method.
- The solution could depend on your needs. What I mentioned above works for me. You might be adding and dynamically removing items hence you might have to do something more then what I did.



The problem I faced was kind of intermittent. I also noticed a race condition which I will explain below ...
- User clicks an item. The clicked item is selected and shown highlighted.
- Tree.commitProperties is called which creates a new dataProvider (if you set the openItems property on the Tree) is created because openItemsChanged is always true.
- dataProvider is set which nulls the selectedItem and selectedIndex.
- You probably have a model where you are tracking the selectedItem.
- You also have a binding Tree.selectedItem = model.selectedItem because the Tree.selectedItem can change for reasons other than tree clicks.
- The binding fires and tries to assign the model.selectedItem to Tree.selectedItem.


There are race conditions happening here. If the model tries to set the selectedItem after the Tree.selectedItem becomes null, you will see something highlighted. Nothing will be shown highlighted if the model sets the selectedItem before the Tree.selectedItem becomes null. callLater can help in these circumstances. I did that and solved the problem but a change somewhere else in the application broke the callLater solution.

With this ended my 18 hour journey to fix a Flex bug that was reported to Adobe 5-6 months before I encountered it. With this ends my first blog too.

Labels: , , ,