5
votes

Inspired by the lazy loading abilities of Hibernate I wanted to make the model part of my Flex UI request data from the server only when necessary. I thought this would be as simple as adding a public accessor that only sends server requests when the variable is accessed.

public function get tab2AC():ArrayCollection
{
    if(_tab2AC == null){
        //Request data from server
    }
    return _tab2AC;
}

Problem is that Flex seems to access all bound variables on application launch, even if the referencing component has yet to be created. So even though the DataGrid with dataProvider="{tab2AC}" has yet to be created, the server request still goes out, thus defeating the "only when required" laziness.

I do not want to place the server request inside a creationComplete handler as I want to keep my UI model ignorant of view state and my view ignorant of server requests.

Interestingly, if I add an Alert.show("anything"); inside the accessor, it works as desired.

UPDATE: Here is a full example. Set breakpoints and you'll see that Flex accesses both variables even though titleForScreen2 is not used by any created component.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
xmlns:s="library://ns.adobe.com/flex/spark" 
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Script>
    <![CDATA[
        private var _titleForScreen1:String;
        private var _titleForScreen2:String;

        public function get titleForScreen1():String {
            if(_titleForScreen1 == null){
                //Server Request
            }                   
            return _titleForScreen1;
        }

        public function get titleForScreen2():String {
            if(_titleForScreen2 == null){
                //Server Request
            }
            return _titleForScreen2;
        }
    ]]>
</fx:Script>

<mx:ViewStack>
    <s:NavigatorContent label="Screen 1">
        <s:Label text="{titleForScreen1}"/>
    </s:NavigatorContent>
    <s:NavigatorContent label="Screen 2">
        <s:Label text="{titleForScreen2}"/>
    </s:NavigatorContent>
</mx:ViewStack>
</s:Application>
6
"Problem is that Flex seems to access all bound variables on application launch, even if the referencing component has yet to be created. " I could use some context for this. What is the referencing component and what is the "Bound variable". One of them must have been created for Flex to access it in some way. If you showed more of your architecture/code perhaps we could explain that.JeffryHouser
if you have a datagrid on your display tree which has a dataprovider, it will try to access the bound variable. Please show more code as to what you're trying to accomplish.J_A_X
Can you post the code where the dataGrid is? Also, can you post more of the code of the data class where the above getter resides? Are you using a custom binding event? You say "here is a full example," but I'm confused as to where here is actually supposed to be.Amy Blankenship
Sorry. Couldn't post full code in comments. See update to question.Gurtnamona
I am not familiar with how NavigatorContent draws its children. Try using a mx Container, which defers instantiation of its children when the creationPolicy is set to "auto". I'll try to make time to dig through NavigatorContent over the weekend.Amy Blankenship

6 Answers

2
votes

Bindings in flex are pretty stupid. More of a proof of concept than an actual optimized production quality feature. What's worse is that short of modifying the compiler, there's little you can do about it without having all sorts of verification logic in your getter or (perhaps more likely) some kind of interceptive layer that makes sure that expensive calls are only made when the UI state is meaningful. At that point however, you might as well do away with bindings altogether and just implement an active controller for a passive view.

I know this is a pretty lame answer, but it's true. I've been a flex developer for several years and have had a complicated relationship with its binding feature for just as long. As well, over all this time, the only thing that has changed in the binding implementation is the ability to do two-way bindings.


By the way, syntactically I'd use a regular method rather than a property for returning a promise. Properties are often read as synchronous and cheap(-ish) operations, whereas a method (especially one that returns a promise) would have more flexible connotations.

1
votes
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
    <fx:Script>
        <![CDATA[
            import mx.controls.Alert;
            private var _titleForScreen1:String;
            private var _titleForScreen2:String;

            public function get titleForScreen1():String {
                if(_titleForScreen1 == null){
                    //Server Request
                }                   
                return _titleForScreen1;
            }

            public function get titleForScreen2():String {
                Alert.show("test");
                if(_titleForScreen2 == null){
                    //Server Request
                }
                return _titleForScreen2;
            }
        ]]>
    </fx:Script>

    <mx:ViewStack>
        <s:NavigatorContent label="Screen 1">
            <s:Label text="{titleForScreen1}"/>
        </s:NavigatorContent>
        <s:NavigatorContent label="Screen 2">
            <s:Label text="{titleForScreen2}"/>
        </s:NavigatorContent>
    </mx:ViewStack>
</s:WindowedApplication>

Breakpoints on lines 12 and 19, check out the stack trace during each, also you can pop open Binding and take a look at wrapFunctionCall (drop a breakpoint in there too). So when it gets to lines 12 and 19, it hits them 2 times because of the preloader dispatching a complete event. I put breakpoints in every file that the stack-trace showed the execution path moving through. Unfortunately I couldn't find the point where it caused 2 calls to happen (must be in the parts I don't have the source for) it seemed every spot in the trace was only called once but I think the wrapFunctionCall was called twice during the period of those two executions. The third one that happens is due to a call to doPhasedInstantation which calls down to execute on Bindings for all the children that have the systemManager, so it would seem somehow the components have a system manager even though they may have not yet been added to the stage or created. Sorry I don't have a more concrete answer for why each of these has to happen but my guess is there's some good reason.

Ah yah almost forgot, also you'll see when the Alert is shown it causes an error but that error is captured in the wrappedFuncitonCall method in Binding.as

catch(error:Error)
    {
        // Certain errors are normal when executing a srcFunc or destFunc,
        // so we swallow them:
        //   Error #1006: Call attempted on an object that is not a function.
        //   Error #1009: null has no properties.
        //   Error #1010: undefined has no properties.
        //   Error #1055: - has no properties.
        //   Error #1069: Property - not found on - and there is no default value
        // We allow any other errors to be thrown.
        if ((error.errorID != 1006) &&
            (error.errorID != 1009) &&
            (error.errorID != 1010) &&
            (error.errorID != 1055) &&
            (error.errorID != 1069))
        {
            throw error;
        }
        else
        {
            if (BindingManager.debugDestinationStrings[destString])
            {
                trace("Binding: destString = " + destString + ", error = " + error);
            }
        }
    }
0
votes

Your statement is not true, tab2AC getter is not accessed by Flex app on launch, as a proof here is the full application code:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;

            private var _tab2AC:ArrayCollection;

            public function set tab2AC(value:ArrayCollection):void
            {
                _tab2AC = value;
            }

            [Bindable]
            public function get tab2AC():ArrayCollection
            {
                if(_tab2AC == null){
                    trace("THIS WILL NOT BE CALLED");
                }
                return _tab2AC;
            }

        ]]>
    </fx:Script>

</s:Application>

As you can see, the trace will not be triggered, so your problem seems to be coming from a call to that getter from somewhere in your app, to find it, put a break-point and then "Step return" when needed.

That being said, you should not implement the lazy loading this way directly in the getter as the service call is asynchronous.

0
votes

Why not return back a new ArrayCollection when the variable is null, then set the source on the ArrayCollection when the server call returns?

0
votes

I think this is just wonky behavior of the debugger, rather than what would happen in ordinary execution. If you put any logic in there that will enable you to determine that the function was called that doesn't tie into the debugger (such as setting the text on a label), then the getter doesn't get called. However, if you put a trace statement in there, the getter does get called.

The conundrum is how much do you want to depend on the idea that this is only going to happen in debugging, and how critical is it to get the real behavior while you are using the debug player?

0
votes

So yeah, that's just the way it is. Since Flex evaluates bindings immediately, I have to delay bindings until creation in order to prevent premature evaluation. Seems like extra work to undo Flex's weird behavior, but that's just how it goes sometimes.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
xmlns:s="library://ns.adobe.com/flex/spark" 
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Script>
    <![CDATA[
        import mx.binding.utils.BindingUtils;
        import mx.binding.utils.ChangeWatcher;

        private var _titleForScreen1:String;
        private var _titleForScreen2:String;

        public function get titleForScreen1():String {
            if(_titleForScreen1 == null){
                //Server Request
            }
            return _titleForScreen1;
        }

        public function get titleForScreen2():String {
            if(_titleForScreen2 == null){
                //Server Request
            }
            return _titleForScreen2;
        }

        public function updateLabel1(value:String):void {screen1Label.text = value;}
        public function updateLabel2(value:String):void {screen2Label.text = value;}

        public function bindLabel1():void {
            var changeWatcher:ChangeWatcher = BindingUtils.bindSetter(updateLabel1,this, "titleForScreen1");
        }

        public function bindLabel2():void {
            var changeWatcher:ChangeWatcher = BindingUtils.bindSetter(updateLabel2,this, "titleForScreen2");
        }
    ]]>
</fx:Script>

<mx:ViewStack>
    <s:NavigatorContent label="Screen 1">
        <s:Label id="screen1Label" creationComplete="bindLabel1()"/>
    </s:NavigatorContent>
    <s:NavigatorContent label="Screen 2">
        <s:Label id="screen2Label" creationComplete="bindLabel2()"/>
    </s:NavigatorContent>
    </s:NavigatorContent>
</mx:ViewStack>
</s:Application>