Home  >  

Handling Delayed Instantiation in Flex 3 (part 2 of 2)

Author photo
AddThis Social Bookmark Button

Last week, we talked about what delayed instantiation is in Flex and why it's used, as well as how to make sure all of the children of a Container are created before you try to address them. If you missed part 1, you may want to review at least the introduction.

This week, we're going to look in depth at the special case of what happens when you use a Repeater to create the children of a ViewStack-based component. By ViewStack-based component, I mean ViewStack, TabNavigator, or Accordion, or custom subclasses of these. I'll admit right now that these problems are not directly related to delayed instantiation, in that you're still going to have them if creationPolicy on the ViewStack is set to "on." However, they are caused because of the way Containers get laid out in expectation of deferred instantiation, so it seemed appropriate to deal with them under this heading.

What's the problem?

So what, specifically is the problem? The problem is that the itemRenderers in the List components that are not on the first tab will be disproportionately tall, even though their contents appear normal size. If you scroll down below the first row (which usually is the full height of the component), then scroll back up, everything lays out properly.

The problem occurs when the following conditions are met:

  1. You have a ViewStack populated by a Repeater
  2. The MXML inside the ViewStack contains a List-Based compoinent (List, HorizontalList, TileList, or DataGrid)
  3. The List-Based component has a percentage width
  4. One or more of the itemRenderers contains at least one Text component whose size is relative to the parent container (i.e., the renderer)

There may be other circumstances that cause this problem, but I know that I have seen the problem with the above. The potential solutions to this one aren't neat and tidy like last week's, so you probably need to understand the whys before getting to the solutions list.

The first key to understanding why this happens is to look at some code from devnet:

 
<?xml version="1.0" encoding="utf-8"?>
<mx:Application 
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    viewSourceURL="src/RepeaterStatic/index.html"
    width="275" height="200" 

>
     <mx:Script>
        <![CDATA[
            [Bindable]
            public var myArray:Array=[1,2,3,4];
        ]]>

    </mx:Script>
    
    <mx:Panel 
        title="Repeater: emulating a for loop"
        paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10"

    >
        
        <mx:Repeater id="myRep" dataProvider="{myArray}"> 
            <mx:Label 
                id="myLabel" 
                text="This is loop #{myRep.currentIndex}"

            />
        </mx:Repeater>
		
    </mx:Panel>
</mx:Application>

You can see that the items being repeated quite often need to reference the repeater itself, and the most currently accessed properties, currentItem and currentIndex, are only available while the repeater is executing. The practical implication of this is that in most cases the children created by a repeater are instantiated right away. However, when a Container (and remember, ViewStack children can only be Containers) created by a Repeater is not the first child of a ViewStack-based control, it measures itself as having a width and height of zero, even though its children already exist.

Since the children already exist, they will set about initializing themselves, including their own children. Since they have a percentage width, they look to their parent to tell them how big to be, and so the parent with no dimensions tells them to be their own minimum sizes. In the case of a List-Based component, the number I have seen most often is 40px. And the List goes on to set up its itemRenderers with the crazy idea that it only has 40 pixels of width to work with, and so it tells the Text component that it has some portion of 40px of width to work with. So let's look at what the docs say about the Text component:

Flex sizes the control to fit the text, with the width long enough to fit the longest line of text and height tall enough to fit the number of lines. If you do not specify a pixel width, the height is determined by the number of explicit line breaks in the text string. If the text length changes, the control resizes to fit the new text.

So, since the Text control is less than 40px wide, when its measure() method is called, it records a tiny width and a really big height. And unless measure() gets called again on the Text control, width and height will continue to contain completely incorrect values.

Let's look at what happens when we switch the tab to bring our second repeated component into view. The Container tells the DataGrid how many pixels its percentage width comes out to. At that point, the DataGrid lays out its renderers again, assigning their explicitWidth, but the renderers don't call measure() again, unless something happens to move them araound, such as scrolling.

How can we fix it?

The simplest fix is probably to set a maxHeight on your renderer class to some acceptable number that doesn't look terrible, but will accommodate your data in most instances. Another simple fix is not to use flexibly sized Text items in itemRenderers–just pick some width that will look good in most instances and go with it.

Another solution is a bit more work, but in some ways it's much neater. Since the Repeater is essentially repeating the same data-driven component, you don't really need to have multiple instances. Instead, you can just create the Container children and "migrate" the List, DataGrid, or whatever, among the different panes, changing the dataProvider as you go. You can see an example of this solution at my personal blog (disclaimer: I had no idea how many problems this avoided when I created the example–I just thought it was a neat idea).

Another solution I have used is to bind the Container that is repeated to its parent's width, using an expression something like width="{width - (viewMetricsAndPadding.left+ viewMetricsAndPadding.right)}". When you do this, each container gets sized immediately and the List-Based component never tells the children they have < 40 pixels to work with.

A final solution occurred to me as I was going through the code to understand why this happens, and that is to override set explicitWidth to call invalidateSize(). The reason I think this should work is that, in UIComponent, the explicitWidth setter calls invalidateSize() on the component's parent, but not on itself. Presumably whoever wrote that had in mind that these components would then do something with that information that would cause invalidateSize() on the component itself to be called, but it seems that does not happen in this instance at least. Since measure() will execute after explicitWidth on the renderer changes, the component should then report its height correctly to the List. I have not tried this solution, but when I get a chance I'll do it and report back.

What about you? Have you encountered this problem? If so, please share your solutions so that others can learn from them.

Read more from Amy Blankenship. Amy Blankenship's Atom feed

Comments

Leave a comment


Type the characters you see in the picture above.


Tag Cloud

Technical Speakers

Who is the best technical speaker you have seen?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.