Flex: understanding databinding: oneway and twoway

17 02 2009

Something that isn’t that well talked about in the Flex world is two way databinding, and it’s implications. 

I was working with an MVC project recently and I came up to an interesting problem with an ArrayCollection somewhere in the View that was bound oneway from the Model. When I changed a property of the object in the View, it changed in the Model. Huh? I didn’t use two-way databinding so why would this happen?

I realised this was due to the fact that while one-way databinding doesn’t change the source when the destination object changes, it DOES ensure that both objects have the same reference. The implication here is that if you change a property of the destination object, it will change in the source as well. They are the same object reference after all.

Let’s prove this with examples. Simple databinding in Flex is trivial. 

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" backgroundColor="white">

<mx:Script>
<![CDATA[

[Bindable]
public var someName:String;

]]>
</mx:Script>

<mx:TextInput text="{someName}" />

<mx:Label text="This is the local obj 'someName': {someName}" />

</mx:Application>

That’s all well and good but what if we want to change the someName variable back? We insert a Binding tag to bind back – two way binding.

<mx:Binding source="textInput.text" destination="someName" />

OK great. But something is happening here that needs to be discussed.  The binding is REPLACING the reference of “someName” to that of a new String. So it may be logical to assume that we need two-way binding when updating the destination of some binding to reflect the changes in the source. But, hang on:

Say I have this Model: 

package
{

	[Bindable]
	public class TestModel
	{
		public static var obj:TestObject = new TestObject("test via model","me via model");
	}

}

And say I have some Object: (TestObject.as)

package
{
	[Bindable]
	public class TestObject
	{

		public var name:String;

		public var desc:String;

		public function TestObject(n:String, d:String)
		{
			this.name = n;
			this.desc = d;
		}

	}
}

And this is my Application:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" backgroundColor="white">

	<mx:Script>
		<![CDATA[

			[Bindable]
			public var someObj:TestObject;

		]]>
	</mx:Script>

	<mx:Binding source="TestModel.obj" destination="this.someObj" />

	<mx:Binding source="textInput.text" destination="someObj.name" />

	<mx:Label id="someLabel" text="From the MODEL: {TestModel.obj.name}" />

	<mx:TextInput id="textInput"  />

	<mx:Label text="This is the name locally: {someObj.name}" />

</mx:Application>

I’m simply binding the Model to the local “someObj” (one-way) and then ensuring that changing the TextInput will change the name property of “someObj”. What happens? Try it. 

testbinding

Notice how my Model is also changing? So, you can see that one-way binding actually ensures that both the source and the destination point to the same object reference. You use two-way binding if you need to change the entire destination object completely and want this to be reflected in the source.

Let’s have a look at the final example:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" backgroundColor="white">

	<mx:Script>
		<![CDATA[

			[Bindable]
			public var someObj:TestObject;

			private function onEnter():void
			{
				someObj = new TestObject("created locally","local");
			}

		]]>
	</mx:Script>

	<mx:Binding source="TestModel.obj" destination="this.someObj" />

	<mx:Binding source="textInput.text" destination="someObj.name" />

	<mx:Label id="someLabel" text="From the MODEL: {TestModel.obj.name}" />

	<mx:TextInput id="textInput" enter="onEnter()"  />

	<mx:Label text="This is the name locally: {someObj.name}" />

</mx:Application>

Now, when you hit “ENTER” on your keyboard aftering entering some text, you’ll see the local object now points to a new object instance. And, you can see the Model doesn’t change from this point on, because it still points to the original instance. If you wanted the Model to update from this change, you would add:

<mx:Binding source="someObj" destination="TestModel.obj" />

 

Although you’d be careful to insert it AFTER the Binding tag that assigns someObj as the destination and TestModel as the source. Otherwise you’ll set the Model to null before you begin!

Make sense?

Advertisements




Flex: Keeping the sort on a datagrid when you change the dataProvider

26 06 2008

Well, there’s a few things you have to dig out a little when it comes to working with the more useful datagrid events.

Say you want to extend the DataGrid into your own component. You want to know when the dataProvider has been changed, and you’d like the new data to be sorted by the previous sort.

The events are as follows:

First, when a dataProvider is changed, the DataGrid instance will fire an mx.events.CollectionEvent.COLLECTION_CHANGE event.

EG:

dataGrid.addEventListener(mx.events.CollectionEvent.COLLECTION_CHANGE, onDataGridDataChange, false, 0, true);

NOTE: Those extra parameters in the attached listener create a “weak reference”. This is to prevent memory leaks.

This signifies that the dataProvider has been changed (should check the “kind” property to ensure it is equal to CollectionEventKind.RESET).

Second, the actual dataProvider itself will fire a COLLECTION_CHANGE itself after it is sorted. In the datagrid listener, you can attach the listener to the dataProvider. When a column heading is clicked, the datagrid will do the sort then call a “refresh()” which will dispatch a CollectionEvent of kind CollectionEventKind.REFRESH.

EG.

private var currentSort:mx.collections.Sort;
private function onDataGridDataChange(evt:CollectionEvent):void
{

                //get the new data collection
                var ac:ArrayCollection = dataGrid.dataProvider as ArrayCollection;
                //attach the collection change event
                ac.addEventListener(CollectionEvent.COLLECTION_CHANGE, onCollectionChanged,false,0,true);

                //if a sort was in place before the data was changed, make sure you apply the sort to this new data
                if (currentSort != null)
                {
                    ac.sort = currentSort;
                    ac.refresh();
                }

}

private function onCollectionChanged(evt:CollectionEvent):void
{

	if (evt.kind == CollectionEventKind.REFRESH)
	{
		var ac:ArrayCollection = evt.currentTarget as ArrayCollection;

		currentSort = ac.sort;

	}

}




Flex/AIR, event listeners, weak references and memory leaks

12 05 2008

I’ve just been reading a book on Flex and came across a great point in the memory management department. That I think everyone should be aware of.

The book is Adobe® Flex™ 3: Training from the Source.

The great point is on memory leaks with event listeners.

Essentially, by creating a generic event listener on some object causes it to continually be referenced even when it is no longer used and therefore the memory is not reused by the Flash Garbage Collector.

Of course you can use the removeEventListener() method, but the point here was that you could also use weak references. Weak references are those that do not cause the GarbageCollector to bypass the memory. Essentially, without a strong reference to an object, the Garbage Collector can reuse that portion of memory next time you request some in Flex by instancing a new object.

You use weak references by calling addEventListener() with extra arguments. For example, if you wanted to handle the Complete event with a weak reference, you’d use:

addEventListener(Event.COMPLETE, onComplete, false, 0, true)

Just use the default values for the 3rd and 4th parameters – useCapture and priority – but use true for weakReferences.

Reading David Coletta’s blog, a very important note is NOT to add a weak reference to an anonymous listener.

Eg. the following code will not work as the anonymous function will be removed because it doesn’t have a strong reference and will go out of scope.

someObj.addEventListener(Event.COMPLETE, function(evt:Event):void{ Alert.show('Complete');}, false, 0, true); //doesn't work!

Another great point is that, in general, member variables do not require attached listeners to be weakly referenced.

This means you don’t have to go out and remove all of those click events on your Buttons declared in MXML, replacing them with ActionScript addEventListener calls because if the listener lives inside the same class, then they both die together.

In other words, if you’re application or component has a child that attaches a listener declared in the same class, then the strong reference won’t inhibit Garbage Collection if that containing instance is removed.

For example: Say I have some component I call MyControl.mxml

<mx:VBox>

<mx:Script>

<![CDATA[

private function doThis(evt:Event):void

{

//…

}

]]>

</mx:Script>

<mx:Button click=”doThis(event)” />

</mx:VBox>

Then I use it in my application thusly: (Application.mxml)

<mx:Application creationComplete=”onCrtComplete(event)”>

<mx:Script>

<![CDATA[

import com.justinjmoses.examples.weakreferencing.MyControl;

private var myCtrl:MyControl;

private function onCrtComplete(evt:Event):void

{

myCtrl = MyControl(this.addChild(new MyControl()));

}

private function removeIt():void

{

this.removeChild(myCtrl);

//now the entire control is available for Garbage Collection

}

]]>

</mx:Script>

</mx:Application> >

Now, when removeIt() is called, the component, MyControl, along with the Button and the listener inside, have their references removed, and are candidates for Garbage Collection.