11 Replies Latest reply: Dec 4, 2012 3:25 PM by Gaius Coffey RSS

    Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection

    JackHombre Community Member

      Whenever I run my program, the console gets spammed w/

      TypeError: Error #1034: Type Coercion failed: cannot convert []@f5a6251 to mx.collections.ArrayCollection.

      and I'd like to do something about that. Unfortunately, I can't find []@f5a6251. So how do I? I have no idea which code executes this.

        • 1. Re: Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection
          Gaius Coffey Community Member

          I know, I know. Irritating when those happen...

           

          If the function was called in your code directly, or by a defined function / object, the compiler would do type checking pre-compile and would give you a warning to check in FlashBuilder.

           

          But...

           

          There are a number of ways this can be bypassed. For example, it happens a lot with Binding where the calls use anonymous functions and are error-trapped in MXML in any case (so errors frequently fail to surface). I find they also occur when dispatching events with similar names and different types.

           

          This type of error is a pig to debug but it is _possible_ to debug. Especially with FlashBuilder.

           

          So...

           

          The laborious way that will guarantee a fix:

           

          What you need to do is to fire up flash builder and open your global search box (Ctrl+H in FB4.6, I think it was something else in earlier FB).

           

          1. Search for arguments defined in your function signatures with datatype ArrayCollection.

          EG: Search for ":*ArrayCollection" to recognise any occurrence of a type declaration of ArrayCollection

          2. Disregard any search results that are _not_ in a function signature

          3. What is left are all the candidates for debug, eg: you should have a list of functions like:

          protected function problemFunction(anArgument:ArrayCollection):void { }

           

          You then just need to look through your list of functions and find any that are called dynamically by asynchronous calls or that are called with untyped data, or that are called by bound calls (label="{calculateSomeStuff(value,value)}" in MXML).

           

          If that doesn't work, simply change the data types of the ArrayCollection function arguments to "Object" or "*" and then either put a break point or throw an error when the data is not an ArrayCollection - either way the FB debugger will stop you on that line of code and allow you to see what called it and why.

           

          The quickest and easiest way that will guarantee a fix if you do it in time

          Of course, the quickest and best way is to test you code frequently and, if this error occurs, to remember what you just changed to introduce the error... and undo that change!

           

          Cheers,

          G

          • 2. Re: Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection
            JackHombre Community Member

            So what you're saying is that if I had done a reg ex search for "function.*ArrayCollection" then I should have got a hit on the problem function? I'm confident that every function definition in my code has everything from "function" to the return value on one line. I've put breakpoints in all of them and the problem does not seem to be connected to the calling of any of them. Are you sure this has to happen during a function call which passes an ArrayCollection? It couldn't happen in a local scope?

            The easiest way won't work, because this isn't my code that does this. The code I inherrited is the quickest, dirties, ugliest I've seen. None of the code I've written since has caused those messages.

            • 3. Re: Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection
              Gaius Coffey Community Member

              TBH, there are more ways than that, but those seemed the most likely.

               

              The thing is, when you have compiled code with properly typed variables, it's very easy for the compiler to see problems and give you a warning - 1067: Implicit coercion of a value of type {whatever} to an unrelated type mx.collections:ArrayCollection. Because you aren't seeing the error 1067, it means the situation isn't explicitly stated in your code at compile time, so must be something that occurs at runtime, when the code is evaluated.

               

              These are the possibiliies:

               

              You have a function like this:

              function myFunction(value:ArrayCollection):void { }

              And something calls it like this:

              myFunction(somethingThatIsNotAnArrayCollection);

               

              Alternatively, you could have something like this:

              package

              {

                        import mx.collections.ArrayCollection;

                public class MyClass

                        { 

              public function MyClass(ac:ArrayCollection) {                    }

              }}

              And something could try to instantiate it like this:

              var ref:MyClass = new MyClass(somethingThatIsNotAnArrayCollection);

               

              Finally, you could have something like this:

              var ac:ArrayCollection = somethingThatIsNotAnArrayCollection;

               

              And to avoid the compiler locating these prior to compile time, you need to have ways for that to occur that bypass the pre-compile type checking.

               

              For example;

               

              var a:Array = someData;

              for(var i:int=0;i<a.length;i++) {

                   var ac:ArrayCollection = a[i]; // no way for compiler to know what type a[i] is so it gives the benefit of the doubt

              }

               

              Or...

              var ac:ArrayCollection = myObject['propertyNameThatRefersToANonArrayCollection'];

               

              Or...

              callLater(myFunction,[aValueOfTheWrongDatatype]);

               

              Or...

               

              You get the gist.

               

              It's a horrid, horrid, horrid bug to have to resolve and I've had one or two too many of them already.

               

              FYI, there is _another_ way for something similar to happen that I had forgotten about, but which only occurs under very specific conditions... Sometimes, when loading a pre-compiled movieclip into another that is running debugger and interacting via code, you get some weird memory space issues so that valid objects in both give a type error like cannot coerce MyObject@[1234] to MyObject or similar... There may be some cross domain stuff happening too, but it disappears when both files are properly compiled for release.

               

              G

              • 4. Re: Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection
                Gaius Coffey Community Member

                PS: Another way I've seen this happening is with metadata declarations... (Though those are usually for events, not for data types.)

                 

                EG:

                [Event(name="bob",type="flash.events.BobEvent")] is defined in MXML but the event that is dispatched is a plain and simple Event so type checking fails.

                 

                Similarly, if you have several handlers for similarly named events, you sometimes get in trouble if they are different event types.

                 

                G

                • 5. Re: Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection
                  JackHombre Community Member

                  Actually, continued sleuthing helped me narrow it down pretty quickly. This app uses RemoteObjects and "method"s to deal with the server which is a big PHP script. Of the many variables passed back (through some "AMF" library) two of them were ArrayCollections, but the only array that PHP arrays seem to have any afinity w/is Array. So just by changing those two things to Arrays, the problem went away. What I find a bit strange is if these things could not be coerced to ArrayCollections, then why didn't the program fail? Those arrays were pretty important, so if the data got lost, it wouldn't go unnoticed.

                  Yes! That's another big problem I've been fighting for a year now: all the ducktyping. Not all of it is our predecessors' fault, since AS is short on enums and coerces a lot of things to Object. But what's less excusable is having a lot of functions in the "main" scope, so every so often a function dereferenced with "Application.appliction" winds up throwing a run-time error. I hate that.

                   

                  Thanks for your help

                  • 7. Re: Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection
                    JackHombre Community Member

                    Do you have any insight into how the coercion failure didn't break anything? I just cleaned up another warning about a bind failure from "length" caused by:

                    <mx:Label text="{(another_control.dataProvider as ArrayCollection).length.toString()}" />

                     

                    by just making the coercion process into a function:

                    private static function DataProvider2ArrayCollection(oDP:Object):ArrayCollection {

                        return oDP as ArrayCollection;

                    }

                    ...

                    <mx:Label text="{DataProvider2ArrayCollection(another_control.dataProvider).length.toString()}" />

                     

                    I get the feeling that all I've done w/these is clean up my debug console.

                    • 8. Re: Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection
                      Gaius Coffey Community Member

                      Ah, now that's different...

                       

                      A binding failure is stating that the property you are referring to is not declared as [Bindable]. Which means that Flex will populate it with the value it has when it comes across it and _never_ update it. Which means, if you instantiate the control and update the thing you've bound, it won't be updated.

                       

                      And that's bad.

                       

                      Essentially: (myValue as ArrayCollection).length is identical to aFunctionThatReturnsValueAsAnArrayCollection(myValue). However, because the compiler sees a function rather than a declaration, it treats it a little differently. But the underlying problem is _still_ there.

                       

                      This will manifest as difficult to trace bugs where code appears to be working, but will frequently display out of date info or fail to respond to things that it absolutely should be responding to. That's the trouble with binding in general; it is so easy to use and so fast that you use it for everything, but if you get it wrong, it has no error checking / reporting / notification so it is really tough to trace the problem.

                       

                      There are a number of ways to resolve the issue that would be better (as in, they might actually _fix_ the underlying issue).

                       

                      1. You could modify the property in the class that declares it to be [Bindable] (if it's a read-only property, you can tie the binding into a relevant event, for example, you could use [Bindable("dataProviderChange")] to update bindings whenever a new Event("dataProviderChange") is dispatched.

                       

                      2. You could set a bound variable on your own class to the dataProvider and bind _that_ instead.

                       

                      3. You could set a bound int to the length when the dataProvider is set...

                       

                      4. Something else...

                       

                      But, and this is the big thing... kill that DataProvider2ArrayCollection function because it is just hiding a problem that you would much rather have fixed!

                      G

                      • 9. Re: Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection
                        Gaius Coffey Community Member

                        ps: For clarity;

                        When I say "set a bound variable" on your class, I mean you need to find a way to set the variable whenever the underlying variable changes...

                        Typically, I would use a getter / setter for this so that when I set the relevantControl property, it will update a number of other relevant properties. That's fine, provided the underlying properties are not going to change, but if they are, then you also need to add listeners to keep your bound variables up to date.

                        Definitely much easier and more robust if the underlying control is declared as [Bindable] and that binding is properly maintained by the underlying control.

                        • 10. Re: Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection
                          JackHombre Community Member

                          I don't think I quite follow. What gets assigned to another_control.dataProvider is always an Array or an ArrayCollection. Since neither of those classes is my racket, I don't quite see how I can apply any of your solutions to the length member. I'm also a bit uncertain about most code involving square brackets that doesn't dereference an array element.

                          • 11. Re: Browsing memory or find out what []@f5a6251 is that it can't coerce to ArrayCollection
                            Gaius Coffey Community Member

                            JackHombre wrote:

                             

                            I don't think I quite follow. What gets assigned to another_control.dataProvider is always an Array or an ArrayCollection.

                            Yes, the binding warning is not the same as the data type warnings.

                             

                            Binding warnings are saying that a value hasn't been declared as [Bindable] and so Flex doesn't know how to keep it updated.

                             

                            EG:

                            A time sequence might be like this:

                            1. myComponent is instantiated, Flex sees value of aControl.dataProvider bound to myProperty so it sets myProperty = aControl.dataProvider

                            2. Some time later, aControl.dataProvider is replaced by a new value, but aControl.dataProvider is not declared as [Bindable] so this change is not propagated to myComponent

                            3. myComponent is now showing stale data and there is no way to refresh it

                             

                            What you want instead is:

                            1. myComponent is instantiated, Flex sees value of aControl.dataProvider bound to myProperty so it sets myProperty = aControl.dataProvider

                            2. Some time later, aControl.dataProvider is replaced by a new value, aControl.dataProvider is  declared as [Bindable] so this change is  propagated to myComponent

                            3. myComponent is now showing current data, everybody is happy

                             

                            There are thousands of different approaches to achieving the latter.

                             

                            For example:

                            [code]

                            public function get boundControl():ThirdPartyComponent { return _boundControl; }

                            private var _thirdPartyComponent:ThirdPartyComponent;

                            public function set boundControl(tpc:ThirdPartyComponent):void {

                                 if(_thirdPartyComponent) // If an old setting is found, clean up first

                                      setThirdPartyListenersAndProperties(_thirdPartyComponent,false);

                                 _thirdPartyComponent = tpc;

                                 if(_thirdPartyComponent) // If a new setting is found, listen for changes

                                      setThirdPartyListenersAndProperties(_thirdPartyComponent,true);

                            }

                            protected function setThirdPartyListenersAndProperties(tpc:ThirdPartyComponent,listening:Boolean):void {

                                 if(listening) {

                                    // Code to listen for changes that would invalidate bound properties, such as new data received that

                                   // overwrites the dataProvider property with an entirely new object

                                   ...

                                    // Set bound properties

                                   var ac:IList = tpc.dataProvider as IList;

                                   if(!ac && tpc.dataProvider && tpc.dataProvider is Array) ac = new ArrayCollection(tpc.dataProvider);

                                   listenedTPCData = ac;

                                  } else {

                                    // Code to remove listeners and clean up for garbage collection

                                   ...

                                    // Code to clear bound properties

                                  listenedTPCData = null;

                                 }

                            }

                            // Probably overkill, but your comments above suggest data may be Array rather than ArrayCollection

                            // so should probably be treated differently to ensure data is valid. I've used datatype IList in case

                            // it is an XMLListCollection or some other collection variant instead

                            [Bindable] private var listenedTPCData:IList;

                            [/code]

                             

                            Also, bear in mind that this binding must be valid all along the chain. For example, if I bind to myControl.dataProvider.length, all three properties this.myControl, myControl.dataProvider and dataProvider.length must be declared as [Bindable] for Flex to follow changes. It doesn't matter that IList.length and myControl properties are bindable if the dataProvider property of myControl is _not_ bindable, the binding will not update if the dataProvider reference is changed.

                            G