Component Array Parameter Gotcha

So the other day, I was working on a component that has an inspectable set/get array property. I could not get it work. Although the default value for this array was not zero length and the parameter in the dialog box was not zero length, the set function was being called with a zero-length array at initialization.

This was an invalid situation and broke my app.

Here's a simplified version of the code:

class Thing extends MovieClip {
 
  private var _myArr:Array;

  ... other code here ...

  <span class ="codehighlight">[Inspectable(defaultValue=[0,255],type="Array"]</span>
  public function set stuff(ar:Array):Void {
    if (ar.length == 0){
      trace("Invalid param.  Array is zero len");
      return;
    }
    <span class="codecomment">// Make a copy of the input array using concat. 
    // Don't want outside code to have a reference 
    // to a private data member.</span>
    _myArr = ar.concat();
    updateDisplay();
  }
  <span class="codecomment">// Again, return a copy of the array so that
  // outside code cannot alter the
  // internals of this component directly.</span>
  public function get stuff():Array { 
    return _myArr.concat(); 
  }
}

Notice that I'm trying to be a good programmer here:

  • I verify the array argument is valid before using it.
  • I don't let code external to this class have a reference to the array data member (I make copies of the array going in and out). This prevents external code from changing the component's guts unbeknownst to the component itself.

In desperation, I decompiled my swf with ASV. I wanted to know how the flash player passes the component dialog box parameters to the component. With the "stuff" parameter set to [45,17,255] in the component property dialog box, here's what I expected:

myThing.stuff = [45,17,255];

But here's what I found:

myThing.stuff = [];
myThing.stuff[0] = 45;
myThing.stuff[1] = 17;
myThing.stuff[2] = 255;

Exercise for the reader: Figure out why my trying to be a "good" programmer was a bad idea.

Comments

I don't use getter/setters for [Inspectable] component parameters, in fact I try to keep them as simple as possible. You have to remember that inspectable params will be set before your class has initialized so you can run into all kinds of problems. Your best bet is to use simple params and onUpdate for live preview (you have to create a compiled clip) and then set your properties manually after initialization, something like this:

class Thing extends MovieClip
{
[Inspectable(name="My Array",defaultValue="0,255")]
private var cp_dataArray:Array;

private var m_dataArray:Array;

public function set dataArray( p_dataArray )
{
m_dataArray = p_dataArray.slice();
}

public function get dataArray()
{
return m_dataArray.slice();
}

private function onUpdate()
{
trace("Array changed: "+cp_dataArray);
}

private function onLoad()
{
dataArray = cp_dataArray;
trace(dataArray);
}
}

By putting the [Inspectable] on a private data member, I don't have to worry about exposing my component's internals. The private data member is only exposed to under-the-hood initialization code.

The setter/getters are still set up to prevent exposing the component's insides.

Makes sense.

Thanks,
Joel