AdobeStock_455007340

Flex Renderers Can't Rely On creationComplete

Yesterday I wasted an hour or so debugging a Flex itemRenderer that I was using to display an image instead of a value in a DataGrid column. The renderer had to simply pick one of six images based on the column value, and so it contained a single tag and a function that set the Image source dynamically. And then I called that function on the renderer’s creationComplete event.
Simple enough. Except that the wrong images were sometimes being displayed, the column had the right data, but the code used to select the image seemed to sometimes pick the wrong image. And what it picked seem to change each time I scrolled the DataGrid up and down!
I actually ran into a very similar issue with a TileList renderer a few weeks ago, but then I had no time to figure out the cause, and so I hacked a workaround. But this time, having been bitten by the same issue twice, I had to find out what was going on.
And what I discovered (by using traces and alerts) is that the creationComplete event does not get fired as I had expected. Rather, it seemed to fire only occasionally, and not once per DataGrid row, and so my image selection function was not being executed as expected.
Once I had figured out the problem I searched the docs for any info on renderers and creationComplete, and found this page. And sure enough, “Flex might reuse an instance of the item renderer or item editor, a reused instance of an item renderer or item editor does not redispatch the creationComplete event”. Well, that explained it.
The right way to do what I wanted is to trap the dataChange event instead of creationComplete, as “Flex dispatches the dataChange event every time the data property changes”.
And so I am posting this for my own future reference, just to make sure I don’t run into it a third time.

26 responses to “Flex Renderers Can't Rely On creationComplete”

  1. Jack Avatar
    Jack

    You nailed a major problem I had with a project recently! Since we were only displaying 4 items at a time, controlling scrolling with custom arrow components, I ended up making a new model with the next 4 items every time the user wanted to move back or forth. Like you, I didn’t have the time to figure out what was wrong. Thanks for posting this!

  2. Joe Rinehart Avatar
    Joe Rinehart

    Hey Ben,
    I went through the same thing when I first picked up Flex 2…once I thought about it, the idea of recycling item renderers made total sense from a performance standpoint.
    Instead of catching dataChange and going through event mechanics, I’ve often found it easier to deal with this issue by updating the setter for the data property that’s on most visual components (I think it’s the implementation of IListDataRenderer?):
    override public function set data(value:Object):void
    {
    super.data = value;
    // do my stuff
    switch (value)
    {
    case "foo":
    myImage.source = "foo.png";
    break;
    }
    }
    I’ll often go further, adding a bindable private property that is of the type of data being set in, allowing components in the renderer to bind cleanly to it.

  3. Ben Forta Avatar
    Ben Forta

    Joe, yep, same here, and that’s exactly what I am doing now. And you actually are indeed catching the dataChange event by overriding set data. One comment though, just to be safe, when overriding set data you should dispatch the DATA_CHANGE event when done, so at the end of your function add:
    dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
    — Ben

  4. João Fernandes Avatar
    João Fernandes

    Ben, if you are doing super.data = value you shouldn’t be required to dispatch the change event since the super class would to that for you.

  5. Ben Forta Avatar
    Ben Forta

    João, cool, I’ll look into that one. Although I wonder why the example on the docs page I mentioned about does both …
    — Ben

  6. Vicente Junior Avatar
    Vicente Junior

    In such cases, I believe "binding" would do the job…
    Every itemRenderer (inline or custom component) has a "data" property linked to the its parent’s dataProvider. So, you can "bind" it to dynamically transfer the corrent row info to the itemRenderer on Flex’s display framework.
    Samples (with sources):
    – inline:
    http://www.vpmjr.com.br/downloads/benforta/itemRenderers001/inline/
    – external component:
    http://www.vpmjr.com.br/downloads/benforta/itemRenderers001/withComponent/

  7. Bryan Bartow Avatar
    Bryan Bartow

    This killed me when I first got into Flex. I spend two weeks trying to figure out the exact same problem. Luckily for me, Alex Harui was very helpful in explaining the idea of itemRenderer recycling to me. I’m glad to see I wasn’t the only one that ran into this problem.

  8. Mary McDonald Avatar
    Mary McDonald

    I am trying to use an external itemrenderer to edit a row of data in a datagrid. The itemrenderer displays a button and when the button is pressed a popup appears with the data. The user updates the data and presses "Save" button in the popup, which triggers the data to be saved in the database and closes the popup.
    It is at this point I am stuck. The datagrid needs to know the popup updated the data. I am not sure how to communicate this. Because of the layers of components I am having trouble communicating back to the datagrid. I tried adding dataChange to the datagrid, but it did not recognize the updated data.
    I can email you the test code as it uses AdventureWorks database if you’d like to see it.
    Thanks,
    Mary

  9. Joe Rinehart Avatar
    Joe Rinehart

    @Ben and Joao – If you’re extending a container like HBox or VBox, Joao is correct – you don’t need to dispatch FlexEvent.DATA_CHANGE. The implementation of set data() in Container itself does this for you. If you’re extending UIComponent (or even Displ

  10. Mary McDonald Avatar
    Mary McDonald

    Joe,
    Could I possible send you my code to take a look at?

  11. Vicente Junior Avatar
    Vicente Junior

    Mary,
    When the item renderer is also a value editor, you’ll need to set "rendererIsEditor=’true’" and set what field on the component modifies the "dataField" property using column’s "editorDataField" property.
    So, in this case, you can make use of custom events too to make the opened window dispatch an event that holds in its properties the new value, so you can listen to it inside your component and set the new value to the property at the component you set up to be the "editorDataField".
    Without looking to what you’re doing, it’s that I can tell you.

  12. Vicente Junior Avatar
    Vicente Junior

    Joe, interesting… thank you…
    I didn’t practice it yet (I’ll do some labs…) but I believe using getter/setter in the component to interface the access to the "data" property, would workaround it. So I just change the bind inside the component to it.

  13. Vicente Junior Avatar
    Vicente Junior

    getter/setter solutions falls on the need to use components "dataChange" event (or another implementation like that), so don’t worths the effort…

  14. Joe Rinehart Avatar
    Joe Rinehart

    @Vicente – there’s no need to use the dataChange event. Here’s what I do, and what I’ve seen others do:
    <mx:Script><![CDATA[
    import model.Contact;
    [Bindable]
    private var contact:Contact;
    override public function set dat

  15. Tink Avatar
    Tink

    Generally we would take care of this by overriding commitProperties().
    When the data prop or listData prop is changed on an itemRenderer it should invalidate its properties by invoking invalidateProperties(), which in turn will invoke commitProperties where you would commit the properties that had changed (i.e. assign the data to you Image).
    Firstly we’d check to see if the data or listData were different (i.e. the value hadn’t just been set again) before invalidating the properties.
    ItemRenderers are re-used in Flex to keep the number of instances created down (i.e. you create enough to display on the screen and re-use them).

  16. Dave Carabetta Avatar
    Dave Carabetta

    If you’re doing anything at all with Flex and item renderers, you literally need to stop what you’re doing and head over to Alex Harui’s blog and reading his series on working with item renderers. I’m not kidding — do not pass go, do not collect $200.
    http://blogs.adobe.com/aharui/2007/03/thinking_about_item_renderers_1.html
    It’s a phenomenal set of entries and his tips are still 100% relevant even with Flex 3 out the door.

  17. David Stockton Avatar
    David Stockton

    This is a very common gotcha I see. It can have some very "interesting" effects 🙂

  18. flex developers Avatar
    flex developers

    hey
    Really nice tips!
    will try to keep it in my mind
    thanks!

  19. Kevin Schmidt Avatar
    Kevin Schmidt

    Ben, why couldn’t you have posted this Monday! I spent about 4 hours Tuesday on the same problem.
    Oh well, not anymore!
    Kevin

  20. Rick Avatar
    Rick

    Why wouldnt you override the updateDisplaylist function instead?

  21. Darin Kohles Avatar
    Darin Kohles

    One thing to keep in mind; dataChange is not only fired when data is set, but also when data is cleared. Therefore you might want to wrap you logic (even with the setter/getter approach) in:
    if( data != null ){
    }
    and if you reuse renderers or the data is complex and/or variable (e.g. sparsely populated XML)
    if( (data != null) && (data.someObject != null) && … (data.someObject.someProperty.@label != null) ){
    }

  22. Carlos Avatar
    Carlos

    Guys I have this issue whenever I try to sort my grid which has 5 columns, and one of them is a component itemrenderer, Ive been looking for hours, Whenever I click on the column header to sort a new BLANK line shows with no data in it, just the progress bar, and the following lines got the same data as the 1st row.
    here’s my code
    <?xml version="1.0" encoding="utf-8"?>
    <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml&quot; minWidth="225" width="225" >
    <mx:Script>
    <![CDATA[
    import mx.events.FlexEvent;
    import mx.rpc.xml.DataType;
    import mx.controls.Alert;
    import com.*.*.lsuNamespace;
    import com.*.*.util.datetime.LSUDateFormatter;
    import com.*.*.common.commonFunctions;
    use namespace lsuNamespace;
    [Bindable]
    private var progressBarColor:uint=0x75ff75; //Default progress bar color (green color) (hexadecimal)
    protected var _mydata:XML;
    override public function set data(value:Object):void
    {
    super.data=value;
    if(value is XML)
    {
    if (value!=null)
    {
    //_mydata=value as XML
    pb.setProgress(Number(value.StepsCompleted),Number(value.StepCount));
    }
    //super.data = value;
    /*if(value)
    {
    pb.setProgress(Number(value.StepsCompleted),Number(value.StepCount));
    }*/
    }else
    {
    _mydata=null
    }
    //dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
    super.invalidateDisplayList();
    }
    private function progressBarStatus(value:Object):String
    {
    if(value is Object)
    {
    var statusStr:String = value.Status.toString();
    switch(statusStr)
    {
    case ‘1’:
    case ‘2’:
    case ‘3’:
    case ‘4’:
    progressBarColor=0xffff00; //Yellow color (Status: Paused)
    return "4 %3%%";
    break;
    case ‘5’:
    //Sets the progress bar to complete
    value.StepsCompleted=100;
    value.StepCount=100;
    data=value;
    progressBarColor=0xff5555; //Red color (Status: Failed)
    return "5";
    break;
    default:
    //if(Number(value.StepCount)==Number(value.StepsCompleted))
    // progressBarColor=0x94d8fc; //Blue color (Status: Completed – 100%)
    //else
    progressBarColor=0x75ff75; //Default color (Green color) (Status: In progress)
    return "%3%%";
    break;
    }
    }
    else
    return "";
    }
    ]]>
    </mx:Script>
    <mx:ProgressBar id="pb" label="{progressBarStatus(data)}" barColor="{progressBarColor}" labelPlacement="center" width="100" left="5" top="0" mode="manual"/>
    <!–mx:Label text="{displayLabel(data.InfrastructureService.LastModifiedEpoch)}" left="113" top="0"/–>
    </mx:HBox>

  23. we3wrwr Avatar
    we3wrwr

    good

  24. Flex developer Avatar
    Flex developer

    Thank you for useful information.

  25. Martin Avatar
    Martin

    I’m having similar issues with ItemRenderers now and I find that it is always safer to override set data. You can analyse the data coming in and update your ItemRenderer accordingly.

  26. Vikas Avatar
    Vikas

    Seems like flex rendering kept you guys busy in solving such issues.

Leave a Reply