Architectural Atrocities, part 8: is there no equality?

The collections in Flex are good as data providers for list and tree components, doing the dirty work of making sure that the components know of changes made to the underlying data, but frankly they suck at most other things. Most importantly they suck at being collections. In this post I’m going to show you why and how to alleviate the problem somewhat.

I’ll let a quote from the Flex Developer’s Guide introduce the problem:

A typical case … is an application that uses windowed paged collections. As the cursor moves through the collection, a particular item might be pulled down from the server and released from memory repeatedly. Every time the item is pulled into memory a new object is created to represent the item.

This is a common problem in all RIA applications: when you load data from a backend you often end up with multiple copies of some objects. In ActionScript there is no built in way of determining equality when it comes to objects (if you think that the === operator solves anything you should read the docs), and neither Flash nor Flex provides any best-practice. Actually, Flex does, almost, but we’ll get to that in a bit.

It is very surprising that Flex has no standard solution for this problem, especially since the documentation acknowledges the problem an also explains how to solve it. Unfortunately the framework does not use the solution to any significant extent. This is the full version of the quote above:

If Flex must consider two or more dfferent objects to be indentical you must implement the IUID interface so that you can assign the same uid to multiple objects. A typical case where you must implement the IUID interface is an application that uses windowed paged collections. As the cursor moves through the collection, a particular item might be pulled down from the server and released from memory repeatedly. Every time the item is pulled into memory a new object is created to represent the item. If you need to compare items for equality, Flex should consider all objects that represent the same item to be the same “thing”.

Right. But, but, but why doesn’t it work that way? The only classes that use UID’s in Flex are the list based components, and then only to keep track of objects and their renderer (and other meta information I guess). In this way the uid property is the same as hashCode in Java, it’s a key to use in hash maps (a.k.a. dictionaries). They don’t use it for things I would consider obvious: like the case where you set the selectedItem property of a list. You would expect the list to find the corresponding item in it’s data provider based on the UID, but no, instead it uses ==. Instant uselessness when it comes to exactly the situation described in the quote above.

In fact, the place where UID’s (or some other mechanism for determining equality, like an equals method à-la Java) is most needed is in collections. The collections in Flex simply ignore this, making methods like contains and getItemIndex meaningless.

Since there is a IUID interface in Flex, whose only purpose is to show that those object implementing it have a custom method for determining equality, you would assume that it was used throughout the framework and especially in places like the collections. In the methods mentioned above it would be quite easy to add a check is the objects being compared both implement IUID then see if their uid properties match, otherwise revert to checking identity. This is not how it actually works — forcing us to implement our own equality testing algorithms. To add insult to injury, Flex doesn’t let us inject those algorithms into a collection. Instead we end up with silly static utility classes where the framework fails us.

It would have been so easy to let us specify a function that would be used for determining equality (which would revert to == if none was specified). The current implementation of getItemIndex in ArrayCollection (arguably the most common collection in Flex) looks like this (it’s actually defined in ArrayUtil and used by way of ArrayList):

var n:int = source.length;

for (var i:int = 0; i < n; i++) { if (source[i] === item) return i; }

return -1;
}

It would have been so much better if it had been implemented like this:

var n:int = source.length;

for (var i:int = 0; i < n; i++) { if ( equalityFunction(source[i], item) ) return i; }

return -1;
}

Where the equalityFunction function was user-definable, like filterFunction. Don’t try to argue that it would be a performance issue, it’s one function call of overhead, and if the compiler was smart (which, granted, it isn’t) it would be completely optimized away. And regardless, I’ll take usefulness over performance any day, because the fact is that you will have to do something similar anyway if you want to find an object in a Flex collection without knowing that you have the exact instance.

The code to make this happen isn’t very long, and it’s provided below. Use it as a replacement for ArrayCollection, no need to change anything else.

BetterArrayCollection

import mx.collections.ArrayCollection;

public class BetterArrayCollection extends ArrayCollection {

public var equalityFunction : Function;

public function BetterArrayCollection( source : Array = null ) { super(source); }

override public function get source( ) : Array { if ( list && (list is SuperiorEqualityArrayList) ) { return SuperiorEqualityArrayList(list).source; }

return null;

}

override public function set source( s : Array ) : void { list = new SuperiorEqualityArrayList(s, equalityFunction); }

} SuperiorEqualityArrayList

import mx.collections.ArrayList;

class SuperiorEqualityArrayList extends ArrayList {

private var equalityFunction : Function;

public function SuperiorEqualityArrayList( source : Array = null, equality : Function = null ) { super(source);

equalityFunction = equality == null
                 ? equalityAsIdentityTest
                 : equality;

}

private function equalityAsIdentityTest( a : *, b : * ) : Boolean { return a == b; }

override public function getItemIndex( item : Object ) : int { var s : uint = this.length;

for ( var i : int = 0; i &lt; s; i++ ) {
  if ( equalityFunction(item, this.getItemAt(i) ) {
    return i;
  }
}

return -1;

}

}

Stick the SuperiorEqualityArrayList class outside the package block in the same file as BetterArrayCollection (or what you want to call it). It’s not needed to be available anywhere else.

If the objects you store in your collections have implemented IUID, you can use an equalityFunction looking something like this:

collection.equalityFunction =
  function( a : IUID, b : IUID ) : Boolean {
    return a.uid == b.uid;
  }

Perhaps you should check for null‘s and whatnot, but I’ll leave that up to you.

I prefer having an equals method, so I would implement it this way:

collection.equalityFunction =
  function( a : *, b : * ) : Boolean {
    return a.equals(b);
  }

Edit (2007-11-29): I just had a look at ListCollectionView.getItemAt after getting some strange errors, and it’s implementation sucks even worse. The problem is that if filterFunction or sort is set it doesn’t use the underlying ArrayList‘s implementation of getItemIndex but instead iterates over all items and tests with ==. There are two ways to fix this: either don’t use filterFunction and sort, or override getItemIndex in your ArrayCollection implementation and make sure it searches correctly.

11 Responses to “Architectural Atrocities, part 8: is there no equality?”

  1. shaunau Says:

    Thanks for the informative and interesting post. Much appreciated.

  2. pjotr Says:

    thanks, very interesting

  3. bjorn Says:

    Thanks for this. I got some strange behaviour as well when using ArrayCollection that has a sort implementation.

    arr.getItemIndex(obj1) always returns -1 even though I see obj1 in the debugger at a specific location in the collection.

  4. 狐狸理财术 » Flex中的对象比较(相等性) Says:

    [...] 国外有篇文章讨论了这一问题:Architectural Atrocities, part 8: is there no equality? [...]

  5. Actionscript collections « Fiji Ecuador Seattle Greece Montana Says:

    [...] http://blog.iconara.net/2007/11/25/architectural-atrocities-part-8-is-there-no-equality/ Possibly related posts: (automatically generated)Object Oriented Perl and OOP FrameworksReconciling MVC with web pagesThe many ways of OOP in perlLinqDataSource not deleting records [...]

  6. tgendler Says:

    great article :)

    I started digging into this exactly because of this: “They don’t use it for things I would consider obvious: like the case where you set the selectedItem property of a list.” Do you have a suggestion for getting around this? This is deep in the implementation of AdvancedListBase and i wouldn’t want to implement all of that, way too risky.

    My current approach is to manually merge the new data into the existing dataprovider so that the instances are the same and then the selectedItem and such will be the same objects. I am a little annoyed at having to do this as the whole uid thing would be such an elegant fix, if only it was actually used by the framework itself!

    I’d be happy to hear your thoughts on this.

    Talya

  7. Theo Says:

    My current approach is to manually merge the new data into the existing dataprovider so that the instances are the same and then the selectedItem and such will be the same objects

    I do this too, it seems like the least bad solution.

  8. benoit Says:

    Hi all, No equality is really an atrocity !

    Do you know if flex sdk 4 or 3.4.0 solve this issue ?

    Thanks.

  9. Theo Says:

    No I don’t know, sorry. I doubt it though.

  10. droopy6 Says:

    you are right, it’s unbelievable but true. even in Flex4. set selectedItem in a spark combobox with a cloned object, the selection will never be set with the right item in the dataprovider. the selection remain empty. baaaad.

  11. Lack of operator overloading in ActionScript 3.0 - Programmers Goodies Says:

    [...] I’ve written some more about this (in the context of Flex) on my blog: Is there no equality?. [...]

Leave a Reply