Archive for the 'Best Practices' Category

05
Nov

Object pooling in Flex / ActionSctipt

Back in March of 2008 I published an object pooling utility class that allow you to place objects into a collection and use them when needed. That class came handy on a recent project where I had to use few instances of DataGrid, which is known to be one of the most expensive components in Flex which many believe should be avoided at all cost. The app I built had to create instances of data grid during runtime and than remove them from the display object. Object pooling is a great design pattern to handle these cases where you need to use the same object over again and again.

The reason is that object creation takes much resources and should be avoided in cases where you need the object often. I looked over the class and although I wrote it in 2008 it is still in good shape. One thing I noticed is that it was using an ArrayCollection. I have upgraded the class to use a Dictionary instead of ArrayCollection using a HashCollection utility class I developed which is using the Dictionary with a weak reference.

Download and look at the ReusablePool utility class:
http://code.google.com/p/eladlib/source/browse/trunk/EladLibFlex/src/com/elad/framework/objectpoolmanager/ReusablePool.as

To implement the class and show you an example I will be using an application that uses three objects: video, image and a list component. When you add the element to the application and then remove it and keep adding these objects agains and again you put a memory expense and you can see a small hickup. However, when you are using Object pool you keep the memory usage low since the objects are cached and you save on the object instantiation cost. As you can see the application is running smoothly while adding and removing objects from the display.

The complete implementation code can be seen below:


<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/halo"
			   minWidth="1024" minHeight="768"
			   creationComplete="setObjects()"
			   viewSourceURL="srcview/index.html">	

     <fx:Script>
          <![CDATA[
               import com.elad.framework.objectpoolmanager.Reusable;
               import com.elad.framework.objectpoolmanager.ReusablePool;

               import mx.collections.ArrayCollection;
               import mx.containers.Canvas;
               import mx.controls.Image;
               import mx.controls.VideoDisplay;
               import mx.core.UIComponent;

               import net.hires.debug.Stats;

               private var reusablePool:ReusablePool = ReusablePool.getInstance();
               private var canvas:Canvas = new Canvas;

               private function setObjects():void
               {
                    // adding stats
                    component.addChild( new Stats() );
                    addElement(component);                         

                    // adding the object to the collection
                    reusablePool.setReusable( new Reusable(createUI(), "FlexImage") );

                    // adding an array object
                    reusablePool.setReusable( new Reusable(createArray(), "arrayList") );

                    // adding video UI object
                    reusablePool.setReusable( new Reusable(createVideoUI(), "videoUI") );                                                            

                    this.addElement(canvas);
               }

               private function useUIObject(name:String):void
               {
                    // getting the collection
                    var reusable:Reusable = reusablePool.acquireReusable(name);
                    var component:UIComponent = reusable.object as UIComponent;

                    // use the object
                    canvas.removeAllChildren();
                    canvas.addElement(component);

                    // return collection back to the pool of objects
                    reusablePool.releaseReusable(reusable);
               }

               private function useArrayObject():void
               {
                    // getting the collection
                    var reusable:Reusable = reusablePool.acquireReusable("arrayList");
                    var dp:ArrayCollection = reusable.object as ArrayCollection;

                    list.dataProvider = dp;

                    // return collection back to the pool of objects
                    reusablePool.releaseReusable(reusable);
               }

               private function noCacheArrayObject():void
               {
                    var dp:ArrayCollection = createArray();
                    list.dataProvider = dp;
               }

               private function noCacheUIObject():void
               {
                    canvas.removeAllChildren();
                    canvas.addElement(createUI());
               }

               private function noCacheVideoUIObject():void
               {
                    canvas.removeAllChildren();
                    canvas.addElement(createVideoUI());
               }     

               private function createUI():UIComponent
               {
                    var image : Image = new Image();
                    image.source = "http://blog.digitalbackcountry.com/wp-content/uploads/flash_builder_logo.png";
                    image.width = 400;
                    image.height = 300;
                    image.x=10;
                    image.y=300;                    

                    return image;
               }

               private function createVideoUI():UIComponent
               {
                    var videoDisplay:VideoDisplay = new VideoDisplay();
                    videoDisplay.source = "http://kakonacl.dip.jp/PlayerTry/TestMovies/FLV/FLV4/Flash.flv";
                    videoDisplay.x = 10;
                    videoDisplay.y = 300;
                    videoDisplay.width = 300;
                    videoDisplay.height = 300;

                    return videoDisplay;
               }

               private function createArray():ArrayCollection
               {
                    var array:ArrayCollection = new ArrayCollection();

                    for (var i:int=0; i<10000; i++)
                    {
                         array.addItem({label: i});
                    }

                    return array;
               }               

          ]]>
     </fx:Script>

     <s:Label x="10" y="10"
                text="These examples shows memory usage with using the object pooling collection and without it."
                fontWeight="bold" height="33"/>

     <s:Label x="10" y="61"
                text="Cached Object Pooling" width="200" fontWeight="bold"/>

     <s:Button x="10" y="87"
                 label="UIobject"
                 click="useUIObject('FlexImage');" width="200"/>

     <s:Button x="10" y="117"
                 label="array"
                 click="useArrayObject();" width="200"/>                  

     <s:Button x="10" y="147"
                 label="Video UI"
                 click="useUIObject('videoUI');" width="200"/>

     <s:List id="list"
               x="325" y="79"
               width="126" height="200" />

     <s:Label x="10" y="178"
                text="Regular objects" width="200" fontWeight="bold"/>

     <s:Button x="10" y="202"
                 label="UIobject"
                 click="noCacheUIObject();" width="200"/>

     <s:Button x="10" y="232"
                 label="Array"
                 click="noCacheArrayObject()" width="200"/>          

     <s:Button x="10" y="262"
                 label="Video UI"
                 click="noCacheVideoUIObject()" width="200"/>

     <mx:UIComponent id="component" x="237" y="83" width="70" height="100"/>     

</s:Application>

screen-shot-2009-11-05-at-104133-pm

To measure the memory usage you can use the small statistic class that was developed by Mr.doob. Additionally, you can take a look at the Eclipse Profiler. In first scenario where I add an element to the application and remove it, the profiler shows as if we have a memory leak since the usage peaked to the “red” lines. I am not saying this is a memory leak, but I am pointing out that the memory usage is high since the objects were removed but haven’t been picked up by the GC yet. As you know using the removeAllChildren() remove the object from the component but is not necessarily ensure the GC will come and release the memory right away. See Profiler screen shot below:

screen-shot-2009-11-05-at-105952-pm

In the second scenario, the objects are cached and I am not putting any memory constrains on my application. See figure below.

screen-shot-2009-11-05-at-110137-pm

The utility class can be limited to the amount of objects you want to store, plus you can tie the class to a logic that will clear all the objects when needed.

Let’s take a look at the code to create an instance of the utility class and add an object.

The utility class is a singleton so we can use the same object through the life cycle of the application.


private var reusablePool:ReusablePool = ReusablePool.getInstance();

To add an object you create a Reusable object that holds the object and the name of the object:


// adding the object to the collection
reusablePool.setReusable( new Reusable(createUI(), "FlexImage") );

When you are ready to retrieve the object use the acquireReusable method, which will remove the object from the object collection:


// getting the collection
var reusable:Reusable = reusablePool.acquireReusable(name);
var component:UIComponent = reusable.object as UIComponent;

When you are done and want to send the object back to the object pool collection use:


reusablePool.releaseReusable(reusable);

Cheers :)

29
Dec

TDD and Asynchronous Tests with Cairngorm applications

Model-View-Controller (MVC) frameworks such as PureMVC or Cairngorm and TDD makes a good marriage. MVC framework works very well with TDD since the application logic is separated from the front view and the data. TDD allows us to create Test Cases on the logic only, as well as create a test case for the presentation layer.

With that said, Cairngorm by default doesn’t separate the view, behavior and model in each container. In order to utilize TDD it’s recommended to use the Presentation model pattern. Few blogs back I covered Cairngorm and the presentation model so in this blog post I am going to continue by showing you how to use TDD and FlexUnit to test your user stories.

The application I am using is simple application with only three business rules:

1. Connect to adobe feeds and retrieve the latest feeds related to Flex.
2. Display these results in a list.
3. Upon user clicking an item on the list display detail information.

Take a look and download the source code:
CairngormPM and TDD

So far you can easily figure out how to create the User Story for item #1, To test Asynchronous tests in FlexUnit we can use the “addAsync” method. Here’s an example of calling the Flex News feeds.


package flexUnitTests
{
    import flexunit.framework.TestCase;

    import mx.rpc.events.ResultEvent;
    import mx.rpc.http.HTTPService;

    public class ServiceTestCase extends TestCase
    {
        private var service:HTTPService;

        public function ServiceTestCase(methodName:String=null)
        {
            super(methodName);
        }

        override public function setUp():void
        {
            service = new HTTPService();
            service.url = "http://rss.adobe.com/en/resources_flex.rss";
            service.resultFormat = "e4x";
        }

        override public function tearDown():void
        {
            service = null;
        }

        public function testServiceCall():void
        {
            service.addEventListener(ResultEvent.RESULT, addAsync(serviceResultEventHandler, 2000, {expectedResults: "Flex News"}, failFunction));
            service.send();
        }

        private function serviceResultEventHandler(event:ResultEvent, data:Object):void
        {
            var val:String = event.result[0].channel.title;
            assertTrue("Unable to retrieve Flex News feeds", val, data.expectedResults);
        }

        private function failFunction(data:Object):void
        {
            fail("Unable to connect to Flex News feeds");
        }
    }
}

Essentially, we should create a test case for each Cairngorm user gesture. So the process is as follow:

1. Understand what the purpose of the user gesture and what model get effected
2. Dispatch the event and watch changes in the model.

Additionally, it’s recommended to create FlexMonkey view tests to ensure the application view is changing according to our requirement and will test the container.

I am not going to explain the entire code, let me cover one test case and you can figure out the rest;

The ReadAdobeFeedsTestCase this test case is based on testing ReadAdobeFeedsEvent. Each Cairngorm Event-Command is based on a user gesture and you need to understand what that user gesture means.

In our case we dispatch the event and on result we place the collection in the model, so we can dispatch the event and make sure the results reaches the model. Here’s the watcher:


watcherInstance = ChangeWatcher.watch(modelLocator.feedsPanelViewerPM,["feedsCollection"],
addAsync(itemsChanged, 2000, {compareResults: 0}, failFunc));
new ReadAdobeFeedsEvent().dispatch();

And once a change is made to the model the binding method will dispatch an event automatically and we can listen to that change and compare the result in the model:


private function itemsChanged(event:Event, data:Object):void
        {
            var len:int = modelLocator.feedsPanelViewerPM.feedsCollection.collection.length;
            Assert.assertTrue("Collection is empty", len>data.compareResults);
        }

Take a look at the complete code:


package flexUnitTests.events
{
    import com.elad.application.events.ReadAdobeFeedsEvent;
    import com.elad.application.model.ModelLocator;

    import flash.events.Event;

    import flexunit.framework.Assert;
    import flexunit.framework.TestCase;

    import mx.binding.utils.ChangeWatcher;

    public class ReadAdobeFeedsTestCase extends TestCase
    {

        [Bindable]
        private var modelLocator:ModelLocator = ModelLocator.getInstance();    

        private var watcherInstance:ChangeWatcher;    

        public function ReadAdobeFeedsTestCase(methodName:String=null)
        {
            super(methodName);
        }

        public function testReadAdobeFeedsEvent():void
        {
            watcherInstance = ChangeWatcher.watch(modelLocator.feedsPanelViewerPM,["feedsCollection"],
                addAsync(itemsChanged, 2000, {compareResults: 0}, failFunc));

            new ReadAdobeFeedsEvent().dispatch();
        }

        private function itemsChanged(event:Event, data:Object):void
        {
            var len:int = modelLocator.feedsPanelViewerPM.feedsCollection.collection.length;
            Assert.assertTrue("Collection is empty", len>data.compareResults);
        }

        private function failFunc(data:Object):void
        {
            fail("Couldn't connect to Adobe feeds and update application model");
        }
    }
}

We have to make some changes in the TestRunner.mxml container.
The reason is that we need to add the same references as we added in our main application to the singleton classes: Front Controller and Service Locator. Otherwise the application will not be able to map between the event and commands as well as lack the ability to make service calls.


<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	xmlns:flexunit="flexunit.flexui.*"
	xmlns:business="com.elad.application.business.*"
	xmlns:control="com.elad.application.control.*"
	creationComplete="onCreationComplete()">

	<mx:Script>
		<![CDATA[
			import flexUnitTests.GesturesTestSuite;

			import flexUnitTests.events.ReadAdobeFeedsTestCase;
			import flexUnitTests.events.UserSelectedFeedTestCase;
			import flexunit.framework.TestSuite;

			private function onCreationComplete():void
			{
				testRunner.test = currentRunTestSuite();
				testRunner.startTest();
			}

			public function currentRunTestSuite():TestSuite
			{
				var testsToRun:TestSuite = new TestSuite();

				testsToRun.addTest(GesturesTestSuite.suite());
				testsToRun.addTest(new UserSelectedFeedTestCase("testUserSelectedFeedEvent"));
				testsToRun.addTest(new ReadAdobeFeedsTestCase("testReadAdobeFeedsEvent"));
				return testsToRun;
			}			

		]]>
	</mx:Script>

	<control:testController />
	<business:Services />
	<flexunit:TestRunnerBase id="testRunner"/>

</mx:Application>

download the source code

I will published a post soon on creating Test Suites and Test Cases for PureMVC, which is much easier than Cairngorm applications since the presentation containers are already split from the data and logic.

04
Dec

Best practices for mapping binary objects from service calls

Working with Remote Object Service invocation utilizing binary connection such as AMFPHP or others, it is often challenging to map the server data with the client data, even when the object is identical in terms of object properties. When you try to cast the object you get a null VO. The reason is that the run-time compiler is unable to cast the object to the VO type.

I was surprised to see experienced Flex developers manually mapping objects directly or creating constructors to the VO to map every single property. Here’s what we shouldn’t do:


var result:ResultEvent = data as ResultEvent;
var user:UserVO = new UserVO();

user.fname = result.result[0].fname;
user.lname = result.result[0].lname;
user.email = result.result[0].email;
etc...

The reason we don’t want to do that, is that this type of approach can work well on a simple VO, however when we have a complex VO with many properties, the code starts to look unprofessional.

I think that as engineers many times we are not sure what’s the best approach/practices, but we can certainly identify wrong approaches. For instance, when we find ourselves doing copy pasting we certainty should find a new approach or if there is no approach available, we can develop our own tool/API to handle the use case.

The right approach is to declare a static method that takes a VO object of type “*” since we don’t know the VO type. Iterate through the collection and map the properties and than return the VO back. Here’s a small utility to handle the mapping:


/*

 Copyright (c) 2008 Elrom LLC. All Rights Reserved 

 @author   Elad Elrom
 @contact  elad.ny@gmail.com

 @internal 

 */
package com.elad.framework.utils
{
	public final class VOHelper
	{
		/**
		 * Method to mpa an object to a VO type.  The are cases where the object returned from the service cannot
		 * cast as the type of VO, this static method will allow you map the VO.
		 *
		 * @example: var user:UserVO = VOHelper.ConvertObjecToVO(result.result[0], new UserVO() );
		 *
		 * @param ob	the object returned from the server.
		 * @return	return the VO object.  Since we want to use many types of VO we use * to generate it dynamically.
		 *
		 */
		public static function ConvertObjecToVO(ob:Object, vo:*):*
		{

			for(var parameter:String in ob)
			{
				try
				{
					vo[ parameter.toLowerCase() ] = ob[ parameter ];
				}
				catch( err:Error )
				{
				}
			}

			return vo;
		}
	}
}

Now you can just use the utility to map the object;


var user:UserVO = VOHelper.ConvertObjecToVO(result.result[0], new UserVO() );