Collections And Chaining For Separate Presentation

02.05.2011

The purpose of this post is to describe the various collection data structures used in Flex and how they relate. Once we understand that, we’ll look at a problem I see commonly in Flex apps regarding separate collection presentations and how to fix it.

Collection Structures

First of all, there are a few “list-like” data structures that you should be aware of. I’ll cover some of the basics of each. I’m going to omit some classes and details for simplicity.

  • Array – A list/collection of objects.
  • ArrayList – Wraps (does not extend) an Array and adds event listeners to all of its items. When the items inside change (are added, removed, or one of their properties is modified), the ArrayList dispatches events so that a component like a List or DataGroup can know it should update its view.
  • ListCollectionView – Wraps (does not extend) an ArrayList and adds sorting, filtering, and cursor functionality. When a filter is applied, all the items remain intact under the hood, it’s just that they’re not “seen” (hence why it’s called a collection view) by outside code. In other words, if a component uses a ListCollectionView as its data provider and the ListCollectionView has a filter, only items passing the filter criteria will show up in the component.
  • ArrayCollection – Extends ListCollectionView and allows you to set its source using an Array instead of an ArrayList. Technically, it just takes the Array and wraps it in an ArrayList for you. The ListCollectionView code takes it from there.

This is the basic relation of these structures. Now let’s look at a couple flex collection interfaces and how they fit in.

  • IList – Spark dataprovider components (List, DataGroup, ComboBox, etc.) take IList structures only. IList provides basic access and manipulation methods based on index and, though the interface can’t enforce it, IList objects generally dispatch CollectionEvent.COLLECTION_CHANGE events to notify the components when internal items have changed so the components can update their views.
  • ICollectionView – Provides methods and properties for filters, sorts, and cursors.

Now let’s bring this all together.

An Array does not implement IList. This means you can’t use it directly in a spark data provider component. Even if you could, if any of its objects were added, removed, or modified, the component would not know about it because an Array doesn’t dispatch events.

ArrayList does implement IList. This means it can be used directly as a data provider component. If you want to send an Array into a spark data provider component and keep things as light as possible, you can wrap it in an ArrayList and send it in. As I said before, ArrayList handles dispatching the events needed for the component to be aware of any changes. One potential downer about ArrayList is it doesn’t support for…each loops.

ListCollectionView also implements IList and also implements ICollectionView. I said before it wraps an ArrayList. While that’s generally true, it really can wrap any IList. So, it’s an IList itself (so it can be used as a data provider) and it wraps another IList. ListCollectionView extends Proxy and adds the needed proxy functions to support for…each loops. In the end, if you don’t need sorts, filters, or cursors, the ListCollectionView is likely extra baggage.

ArrayCollection extends ListCollectionView which likewise means it implements IList and ICollectionView and inherits ListCollectionView’s functionality.

I’ve explained these things so you (1) will have a better understanding of what you’re using, (2) can make more efficient use of the structures, (3) and can make structures of your own that better suite your needs.

Chaining Collections for Separate Presentation

One other reason is because it comes into play in a common problem I’ve seen in Flex apps. Here’s an all-too-common scenario: an app loads a list of widgets from the server as an ArrayCollection which it then places on a model that’s generally available in the app. In the store portion of the app, users can filter widgets by name by typing in a filter text input. When the user types a few characters, the store filters the ArrayCollection (the original one pulled from the server) down to only those widgets whose name matches the user’s input.

At this point or shortly thereafter, some code somewhere else in the app inevitably expects the ArrayCollection to not be filtered. Maybe it’s a different view in the app that always shows all widgets–or at least is supposed to. Because the ArrayCollection has been filtered and is shared in both views, both views show the filtered widgets.

Or…maybe some code in another portion of the app attempts to access one of the widgets from the ArrayCollection. If the widget it’s trying to access has been filtered from of the ArrayCollection’s “view” (not to be confused with a user interface) by the store, there are going to be issues.

So, what’s the solution? Use a separate ICollectionView when supporting sorts or filters that shouldn’t affect other parts of the app. The underlying data model for the view, however, can be shared. In the example, you would instantiate a new ArrayCollection or ListCollectionView and set the list property using same the list which the original ArrayCollection wraps. Since both ICollectionViews now wrap the same ArrayList, if you add or remove an object from the original, app-wide collection it will also do likewise on the store-only collection. However, if you set a filter or sort on the store collection, it will not apply the filter or sort on the app-wide collection. This is exactly what we want.

Code says a thousand words, so let’s see this in action.

<?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"
					   initialize="createBaseCollection();">
	<fx:Script>
		<![CDATA[
			import mx.collections.ArrayCollection;
			import mx.collections.ListCollectionView;
 
			[Bindable]
			protected var baseCollection:ArrayCollection;
 
			[Bindable]
			protected var filteredCollection:ListCollectionView;
 
			protected function createBaseCollection():void
			{
				baseCollection = new ArrayCollection();
 
				for (var i:uint; i < 100; i++)
				{
					var widget:Widget = new Widget();
					widget.name = "Widget " + (i + 1);
					widget.price = Math.random() * 100;
					baseCollection.addItem(widget);
				}
			}
 
			protected function createAndFilterSeparateCollection():void
			{
				filteredCollection = new ListCollectionView();
				filteredCollection.list = baseCollection.list;
 
				filteredCollection.filterFunction = filterWidgets;
				filteredCollection.refresh();
			}
 
			protected function filterWidgets(item:Widget):Boolean
			{
				return item.price > 50;
			}
 
			protected function addWidget():void
			{
				var newWidget:Widget = new Widget();
				newWidget.name = 'NewWidget';
				newWidget.price = 90;
				baseCollection.addItemAt(newWidget, 0);
			}
		]]>
	</fx:Script>
 
	<s:layout>
		<s:VerticalLayout/>
	</s:layout>
 
	<s:Button label="Create Filtered Collection" click="createAndFilterSeparateCollection()"/>
	<s:Button label="Add Widget" click="addWidget();"/>
 
	<s:HGroup>
		<s:List dataProvider="{baseCollection}" labelField="name"/>
		<s:List dataProvider="{filteredCollection}" labelField="name"/>
	</s:HGroup>
</s:WindowedApplication>

In the code sample, we see our baseCollection being created within createBaseCollection(). This represents the ArrayCollection that’s being shared throughout the app. This doesn’t need to be an ArrayCollection; it could just be a ListCollectionView or ArrayList. The baseCollection then shows up in the left list component. When you click on the Create Filtered Collection button, it calls createAndFilterSeparateCollection() which creates a separate ListCollectionView using the underlying ArrayList that baseCollection is using.

This is important. This is a new, separate collection view for the same underlying data model. When this new collection view is filtered, it doesn’t affect baseCollection. This means the left list component will show all widgets while the right list component will show a only a subset of the widgets. But, because they use the same underlying data model (ArrayList), if we add a widget to baseCollection, baseCollection will add it to the underlying data model, which will notify filteredCollection. If filteredCollection finds that the widget passes the currently applied filter criteria, the widget will show up in its collection view. This is why, if you click the Add Widget button, you will see the new widget show up in both list components even though addWidget() is only directly adding the widget to baseCollection.

I hope this helps someone out there avoid collection view woes in their app. Be sure to add a comment if I messed something up or you have a question. Good luck!

Tags: , , , , , , ,


Comments

07.20.2011 / Abiyasa said:

Thanks for the great post! Didn’t know that you cannot use for..each on ArrayLlist.

The bad thing is that you still can use for..each on ArrayList without any compilation error and your app is still running, except that the loop will not run at all.

03.05.2012 / Anthony Martin said:

Thanks,

This solved a big problem for my air app I am building.

05.04.2012 / David said:

Nice, exactly what I was looking for

06.12.2012 / Brian Bishop said:

Good explanation of the differences, especially the summary at the top with ListCollectionView etc. The under-the-hood bit was what I was looking for:)

10.20.2012 / Akhil Mittal said:

Thank you very much. This is really a very nice explanation.

10.21.2012 / Frans Maas said:

Really very clear and useful explanation.
I badly needed this functionality, felt intuitively that it had to be possible, but failed to find it in the api reference myself.
It saves me creating redundant collections and managing drag-and-drop across those instances.
One big collection together with the right filter now is all I need.
Thanks a lot!!

03.11.2015 / Robert said:

Really great explanation and demonstration of some very useful code


Leave a Comment

Your email address is required but will not be published.




Comment