Tom Sugden: Components Archives

April 16, 2010

Optimizing the Flex DataGrid for Frequent Updates

The default behaviour of a Flex DataGrid is to redraw itself entirely when an item in its data provider changes. This makes good sense in some cases, since the item renderers might need to grow or shrink in response, but at other times it can be problematic. If subsets of the data provider change frequently, like in a real-time price grid, excessive redrawing and increased CPU can occur. This blog post explains a simple technique for overriding the default behaviour to support frequent updates with reduced CPU.

DefaultAndOptimizedDG.png

Figure 1: Redraw regions of default (left) and optimized (right) data grids

You can download an example Flash Builder project to accompany this blog post:

Thanks to Christophe Coenraets for first showing this DataGrid optimization technique to me.

Understanding the Default Behavior

Let's say you're rendering a data provider full of simple, bindable value objects. Each time one of the properties of an item changes, the auto-generated binding code dispatches a propertyChange event. This is heard by the enclosing collection, triggering a collectionChange event of the update kind. Then deep within the DataGrid and its baseclass, ListBase, an invalidation occurs:

protected function collectionChangeHandler(event:Event):void
{
    ...
    itemsSizeChanged = true;

    invalidateDisplayList();
}

So the DataGrid will be redrawn during the next update. In the worst case scenario, one or more items of data might change during each frame interval, causing the grid to invalidate itself and be redrawn on every frame. This would increase CPU.

Overriding the Default Behavior

The default behaviour of the DataGrid can be overridden so that item renderers invalidate themselves independently and only when changes to their individual data items occur. This optimization usually involves two steps:

  1. Extend DataGrid to override collectionChangeHandler() to ignore collection change events that arise from updates to items in the data provider.
  2. Write a custom item renderer that handles its own invalidation when it detects changes in its data item.

Extend DataGrid

Shown below is a simple extension of DataGrid that overrides the collectionChangeHandler() function to ignore collectionChange events with the update kind.

public class FastDataGrid extends DataGrid
{
    override protected function collectionChangeHandler(event:Event):void
    {
        if (event is CollectionEvent &&
            CollectionEvent(event).kind != CollectionEventKind.UPDATE)
        {
            super.collectionChangeHandler(event);
        }
    }
}

This change stops the DataGrid redrawing excessively when frequent changes occur, but it needs to be complemented with a modified item renderer that knows how to invalidate itself.

Note that this customization is only necessary if the value objects in your data provider are dispatching propertyChange events. If you're using custom events instead, then the collection will ignore these and so the invalidation of the DataGrid will not be triggered.

Self-Invalidating Item Renderer

Shown below is a custom ActionScript item renderer based on UIComponent containing a single UITextField. The item renderer listens to its data item for matching propertyChange events and invalidates itself when they occur.

public class PropertyChangeRenderer
    extends UIComponent 
    implements IDropInListItemRenderer, IListItemRenderer 
{
    private var textField:UITextField;
    private var column:DataGridColumn;
    private var updateText:Boolean;

    //-------------------------------
    //  listData
    //-------------------------------

    private var _listData:DataGridListData;

    [Bindable("dataChange")]
    public function get listData():BaseListData
    {
        return _listData;
    }

    public function set listData(value:BaseListData):void
    {
        _listData = value as DataGridListData;
        column = _listData 
            ? DataGrid(_listData.owner).columns[_listData.columnIndex] as DataGridColumn 
            : null;
    }

    //-------------------------------
    //  data
    //-------------------------------

    private var _data : Object;

    public function get data() : Object
    {
        return _data;
    }

    public function set data(value:Object):void
    {
        if (_data == value) return;

        if (_data && _data is IEventDispatcher)
        {
            IEventDispatcher(_data).removeEventListener(
                PropertyChangeEvent.PROPERTY_CHANGE,
                propertyChangeHandler);
        }

        _data = value;
        updateText = true;

        if (_data && _data is IEventDispatcher)
        {
            IEventDispatcher(_data).addEventListener(
                PropertyChangeEvent.PROPERTY_CHANGE,
                propertyChangeHandler,
                false, 0, true);
        }

        dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
    }

    private function propertyChangeHandler(event:PropertyChangeEvent):void
    {
        if (event.property == _listData.dataField)
        {
            updateText = true;
            invalidateProperties();
        }
    }

    //---------------------------------------------------------------------
    //
    //  Overrides : UIComponent
    //
    //---------------------------------------------------------------------

    override protected function createChildren():void
    {
        super.createChildren();

        textField = new UITextField();

        // hardcoded size and position to confine the redraw region, 
        // since it never changes in this example
        textField.width = 130;
        textField.height = 16;
        textField.x = 3;

        addChild(textField);
    }

    override protected function commitProperties():void
    {
        super.commitProperties();

        if (updateText)
        {
            updateText = false;
            textField.text = column.itemToLabel(data);
        }
    }
}

Some compromises are made in this item renderer. It's not as general-purpose as the DataGridItemRenderer and its creation-time is going to be higher, since it uses a UIComponent to contain a UITextField, whereas the DataGridItemRenderer simply extends UITextField. However, at runtime the CPU will be reduced, since only a region of 130x16 will be redrawn for each property change instead of the whole grid.

Sample Application

The FastDataGrid.zip project contains an application, FastDataGridExample.mxml, that demonstrates the default and optimized data grids. You can use this as a starting point to refine the grid and item renderer and perhaps make further optimizations or better generalizations. The project contains two implementations of the item renderer with comments describing the differences.

Conclusion

If your application contains data grids that change frequently, use the "Show Redraw Regions" feature of Flash Debug Player to make sure that excessive redrawing is not taking place. If so, consider optimizing the data grid and your item renderers to minimize redrawing and reduce CPU. The same technique described in this blog post also applies to the AdvancedDataGrid.

Posted by tsugden at 10:16 AM | Comments (2)

December 10, 2009

Writing Genuinely Reusable Flex Components

On larger projects and within enterprises, there's often a case for extracting a set of reusable components into a Flex library project. In theory, the same components can be reused across modules and sub-applications of multiple Flex or AIR clients, bringing greater consistency and more rapid development. However, in practice there are some common mistakes that limit the reusability of components. This post explains what makes a component genuinely reusable and highlights some techniques from the Flex SDK that can be applied to your own components to make them more reusable.

What Makes a Component Genuinely Reusable?

There are different levels of reusability, but a fully reusable component should be able to render any kind of data. It should be equally comfortable with an array of basic, dynamic Objects or a collection of concrete Kangaroos. The Flex DataGrid has this quality:

<mx:DataGrid dataProvider="{ kangaroos }">
   <mx:columns>
      <mx:DataGridColumn headerText="Name" dataField="name"/>
      <mx:DataGridColumn headerText="Weight" labelFunction="calculateWeight"/>
   </mx:columns>
</mx:DataGrid>

Notice how the dataField and labelFunction properties tell the component how to get its data from the Kanagoo objects without imposing any dependency. These are two of the mechanisms available to make a component genuinely reusable. Even if the developer has no control over the Kangaroo class itself, perhaps it's part of a 3rd-party library, they can still easily render these objects within a DataGrid.

The Data Interface Anti-Pattern

One common mistake is to insist that the data being rendered by a component implements a specific interface. For example, consider a DistributionBar component that renders a simple graph, like that shown in Figure 1.

DistributionBar.png

Figure 1 - A Distribution Bar Component

The distribution bar shows a number of regions with different sizes, each containing a label. It's tempting to configure this using an array of IRegion objects:

public interface IRegion
{
   function get label() : String;
   function get size() : int;
}

The distribution bar can then extract the size and label information for each region through this interface. The rationale for this is that the interface decouples the component from the concrete objects it renders. Anything can be rendered so long as it implements IRegion, but that so long is in fact the design flaw. By imposing the use of the IRegion interface the reusability is limited. The interface needs to be added to existing model classes before they can be rendered in a distribution bar, and worse, if the models were produced by another library or separate team, it might not be an option to change them, so they'd need to be wrapped. For these reasons, the component is not genuinely reusable.

Reusable Components of the Flex SDK

The Flex SDK contains many reusable components and this is achieved by applying a few standard approaches:

  1. Data Fields
  2. Data Functions
  3. Data Descriptors
  4. Factory Objects

These approaches are now described and the same techniques can be applied to your own components to make them reusable.

Data Fields

A data field is a String property that specifies the name of another property. For example, the labelField property of the ComboBox or the dataField and dataTipField properties of the DataGridColumn:

<mx:ComboBox dataProvider="{ items }" labelField="name"/>

The component implementation uses the data field to read the data values from the items it renders. For example:

for each (var item:Object in dataProvider)
{
    var value:Object = item[dataField];
    // do something with the value
}

This is a simple approach but it offers great flexibility. The component can render any readable property of any class of object.

Data Functions

A data function is a property of type Function that is used to specify a reference to another function. For example, the labelFunction property of the ComboBox or the dataFunction property of the DataGridColumn.

<mx:DataGridColumn headerText="weight" dataFunction="calculateWeight"/>

The component then invokes the data function, typically passing through an item of data as a parameter. For example:

for each (var item:Object in dataProvider)
{
    var value:Object = dataFunction(item);
    // do something with the value
}

This approach is similar to using a data field, but offers even more flexibility, since the function can perform calculations or formatting before returning a value to the component for rendering.

Data Descriptors

A data descriptor is an interface through which a component can analyze the items of data it renders. The developer can then pass their own implementation of the interface to the component in order to configure it. An example can be seen in the Tree component of the Flex SDK:

<mx:Tree dataProvider="{ items }">
    <mx:dataDescriptor><my:MyDataDescriptor/></mx:dataDescriptor>
</mx:Tree>

The tree can then discover characteristics of the data by querying its data descriptor interface. For example:

for each (var item:Object in dataProvider)
{
    var isBranch:Boolean = dataDescriptor.isBranch(item, dataProvider);
    // do something with the outcome
}

This approach is very powerful, but is only necessary for complex components such as the Tree. The effort of using the component is more than a simple List or ComboBox, but the component is still completely decoupled from the data it renders. If a developer needs to render a new class of object in a tree, they typically write a new implementation of the ITreeDataDescriptor interface.

Factory Objects

A factory object, in terms of component developent, is a property of type IFactory that is used to instantiate children at runtime. For example, the itemRenderer property of the List and DataGrid or the dropdownFactory property of the ComboBox.

<mx:List dataProvider="{ items }" itemRenderer="my.package.MyItemRenderer"/>

The component uses the standard IFactory interface of the Flex SDK to create new objects at runtime:

var itemRenderer:Object = itemRenderer.newInstance();

Then it passes data items into the new object through the IDataRenderer interface:

if (itemRenderer is IDataRenderer)
{
    IDataRenderer(itemRenderer).data = item;
}

This approach gives great control over the visual appearance of parts of the component. By providing custom item renderers, completely different results can be achieved. The logic for processing the item of data can be as simple or complex as needed and it can be encapsulated inside the item renderer class. Having said that, components that use factories should define sensible default values, so the component is easy to use in simple cases without setting special factories. This is the case for all the ListBase components, such as DataGrid, which uses the general purpose DataGridItemRenderer by default.

It's worth noting here that the Flex compiler has a special relationship with properties of type IFactory. When it notices such a property being set in MXML, it will automatically generate code to convert class names and in-line components into instances of ClassFactory. This makes the components easier to use, so developers don't usually need to instantiate class factories manually, but instead just specify a class name or declare an in-line component.

Conclusion

When fully reusable components are needed, it's good to remember the simple rule: a reusable component should be able to render any kind of data. This is best achieved by following the conventions laid out in the Flex SDK, such as data fields, data functions, data descriptors and object factories. It's important to resist the urge to introduce new interfaces that impose unnecessary obligations on users of your component, because to do so limits its reusability.

Postscript: Since fully reusable components tend to use mechanisms such as dynamic property lookups and function references, there are some trade-offs to consider. These techniques are slower than accessing properties of strongly-typed objects and they're not resiliant to compile-time type checking. However, the advantages of flexibility and reduced dependencies can outweigh these drawbacks for larger projects and enterprises.

Posted by tsugden at 9:18 AM | Comments (2)

July 18, 2009

A Declarative Approach to Flex Popups

Yaniv De Ridder and myself have developed a small Flex library for opening and closing popups. Instead of using the PopUpManager directly and writing script-block logic to manage their creation and removal, a pair of simple MXML tags are available for declaring within view components. Here's the "Hello World" of declarative popups:

<popup:PopUpWrapper open="{model.openPopUp}">
   <mx:Label text="Hello World"/>
</popup:PopUpWrapper>

The PopUpWrapper tag is a non-visual component that manages opening and closing the popup. When its open property is set to true, a popup is opened containing the component wrapped by the tag; in this case a Label. When the open property is set back to false, the popup closes again. Alternatively, the component may dispatch an Event.CLOSE event, which will be handled by the PopUpWrapper itself.

This approach helps to keep MXML views components clean and free from ActionScript logic, whilst removing duplicated code wherever the PopUpManager is needed. The opening and closure can be controlled conveniently through bindings, as above, which plays nicely with presentation models. There are also simple ways to control the life-cycle of the popup and to apply special behaviors, such as effects that play while it opens and closes.

The remainder of this post covers the two components available -- PopUpWrapper and PopUpFactory -- explaining the differences between them. The library, source code, unit tests and a sample application are available for download here:

Using Declarative Popups

There are two components with slightly different capabilities:

  • PopUpWrapper - this is the simplest tag. It defers the creation of the popup view until the first time the popup is opened, then reuses the same view every subsequent time. There is no mechanism for releasing that view to be garbage collected.
  • PopUpFactory - this is the more flexible but less simple tag. It also defers creation of the popup view, but gives control over whether or not the view should be reused when the popup is next opened. If reuse is disabled, the view is made eligible for garbage collection upon closure.

Some examples of each are now provided.

The PopUpWrapper Component

Here's a more detailed version of the "Hello World", this time showing all of the properties and events available:

<popup:PopUpWrapper
    open="{model.openPopUp}"
    center="true"
    modal="false"
    childList="{PopUpManagerChildList.POPUP}"
    opening="openingHandler()"
    opened="openedHandler()"
    closing="closingHandler()"
    closed="closedHandler()">
    <mx:Label text="Hello World!"/>
<popup:PopUpWrapper>

The properties provide the same control over the popup as the PopUpManager. The events are dispatched at various points during the life-cycle of a popup:

  • PopUpEvent.OPENING is dispatched after the popup view has been created but just before it has actually been added to the display list.
  • PopUpEvent.OPENED is dispatched just after the popup view has been added to the display list.
  • PopUpEvent.CLOSING is dispatched just before the popup view is removed from the display list.
  • PopUpEvent.CLOSED is dispatched just after the popup view has been removed from the display list.

Here's another example where the popup is opened or closed using buttons instead of binding. The view used for the actual popup is a custom component.

<popup:PopUpWrapper id="popup">
    <mypopup:MyPopupView/>
</popup:PopUpWrapper>

<mx:Button label="Open" click="popup.open = true"/>
<mx:Button label="Close" click="popup.open = false"/>

The popup view itself can also instruct the closure of the popup by dispatching an Event.CLOSE event. For example:

<mypopup:VBox ... >
    <mx:Label text="My Popup!"/>
    <Button 
        label="Close" 
        click="dispatchEvent(new Event(Event.CLOSE))"/>
</mypopup:VBox>    

The PopUpFactory Component

The PopUpFactory tag uses a different mechanism for specifying the popup view. It is in fact the same approach that list-based controls use for their item renderers. The factory property can either be set to the class name for the popup view, or else an in-line component can be declared.

Here is a simples use case, specifying the popup view class name:

<popup:PopUpFactory 
    open="{model.openPopUp}"
    factory="my.package.MyPopupView"
    reuse="false"/>

Notice that the PopUpFactory includes a reuse property. This gives control over whether or not the popup view is reused, in contrast to the PopUpWrapper tag which always reuses the view and never makes it eligible for garbage collection.

When working with the PopUpManager directly it is common to make popups eligible for garbage collection once they have been removed. However there are good use cases for both approaches. If an application shows a large popup at start-up and then never again, it is wasteful of resources to keep it in memory. Alternatively, if a popup is repeatedly shown and hidden, such as a context menu, it is more efficient to reuses a single instance. As with all Flex development, the Flex Profiler should be used to ensure popup views are successfully garbage collected, since they can be a source of memory leaks.

Here is another example where the popup view is declared as an in-line component, in a similar manner to an item renderer on a list-based control.

<popup:PopUpFactory 
    open="{model.openPopUp}"
    reuse="false">
    <mx:Component>
        <mypopup:MyPopupView
            title="{outerDocument.model.popUpTitle}"/>
    </mx:Component>
</popup:PopUpFactory>

The PopUpFactory tag also provides the same properties as the PopUpWrapper tag, since they both inherit from a common base class.

Popup Behaviors

When we first introduced a declarative approach for popups to our projects, we noticed developers customizing the classes with more and more specific behavior. In one situation, a popup needed to play a transition while opening; in another the popup had to remain centered whenever its contents resized. Soon the popup tags became bloated with code for handling all of these special requirements, each applicable only to one or two popups.

The IPopUpBehavior interface was introduced to extract these specialities into their own classes. In this way, a set of behaviors can be created, independent of one another, and the appropriate behaviors can be applied for a given situation. This design creates a nice extensibility point, allowing developers to write their own behaviors without needing to customize the declarative tags at all.

Here is an example of a popup with a number of behaviors applied.

<popup:PopUpFactory
    open="{model.showPopUp}"
    factory="my.package.MyPopUpView">
    <popup:behaviors>
        <mx:Array>
            <popup:ZoomAndFadePopUpBehavior/>
            <popup:KeepCenteredPopUpBehavior/>
        </mx:Array>
    <popup:behaviors>
</popup:PopUpFactory>

In this case, two custom popup behaviors are being applied. The first plays a zoom and fade effect upon opening and closure, while the second ensures that the popup remains centered whenever its contents is resized. The declarative approach makes it simple to configure these behaviors.

To write your own popup behavior, just implement the IPopUpBehavior interface:

public interface IPopUpBehavior
{
    function apply(opener:PopUpBase):void;
}

A reference to the popup component is passed through the apply() method to each behavior. The behavior can then attach event handlers in order to control the behavior of the actual popup while it is opened or closed. Some examples are provided in the sample project (download above). A behavior can also suspend and resume closure by calling the suspendClosure() and resumeClosure() methods of the PopUpEvent class. This allows something asynchronous, such as the playing of an effect, to take place during closure, before the popup is actually removed from the display list.

How Does It Work?

The design for the popup tags is relatively simple. There's a common base class -- PopUpBase -- that is extended by the two tags: PopUpWrapper and PopUpFactory. The base class contains the common properties -- open, center, modal -- and also holds an array of behaviors, applying them during opening and closure. The two concrete tags implement different creation and destruction policies using the standard mechanisms for deferred instantiation and object factories: IDeferredInstance and IFactory. The Flex compiler takes care of the rest, converting the declared popup view into an appropriately configured DeferredInstanceFromFunction/Class or ClassFactory object. See the Flex Language Reference for more details.

Conclusion

We've been using versions of these tags on our projects for several years now. They help us to separate concerns, keeping our views as simple declarations of components, free from script block logic that would make them harder to understand and more difficult to unit test.

It's worth noting that Flex 4 includes its own declarative tag -- PopUpAnchor -- which also takes advantage of MXML and deferred creation, but it is designed for drop-down menu scenarios. There are some similarities, but the PopUpWrapper and PopUpFactory components are designed for more general popup handling. Perhaps a future version of the SDK will include declarative tags that make it simple to manage the life-cycle of popups and apply different behaviors, but in the meantime we hope you enjoy using these components.

Posted by tsugden at 10:07 PM | Comments (5)