Archive for December, 2008

30
Dec

FlexUnit Asynchronous tests with PureMVC Framework

There are very little resources online in term of how to use FlexUnit with PureMVC and TDD, furthermore in many cases it makes more sense to create the tests after user stories are implemented.

Let’s take the application we created for Cairngorm and convert it to PureMVC framework. This is a good exercise since it can help define the different between the two frameworks.

Creating tests with existing framework may be challenging at times. In fact, there are cases where it’s makes more sense to create the test after the code is completed rather than following common TDD practices.

TDD recommend to create the test before writing the code, however working in real life application and with exsiting frameworks you may find that sometimes it’s easier to write the code before the test, especially when you use plug-ins or code generator scripts to create your user gesture automatically. I recommend to use your own judgment and see what’s works best for you.

Take a look at the post I published yesterday:
http://elromdesign.com/blog/2008/12/29/tdd-and-asynchronous-tests-with-cairngorm-applications/

View application and download the source code:
CairngormPM and TDD

In this example I am using the same FeedsPanelViewer.mxml container I created for the Cairngorm application. However, I made few small change to the container. I decided to remove the binding properties and let the mediator handle all the changes in the container.


<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
     layout="vertical"
     width="600" height="450"
     styleName="panelView">

     <!-- Feeds List -->
     <mx:List
          id="feedList"
         change="dispatchEvent(new UserSelectedFeedEvent(FeedVO( feedList.selectedItem )));"
          labelField="title"/>

     <!-- Detail information -->
     <mx:VBox width="540" horizontalScrollPolicy="off" verticalScrollPolicy="off">
          <mx:Spacer height="15" />
          <mx:HBox>
               <mx:Label text="FEED DETAIL:" fontWeight="bold"/>
          </mx:HBox>
          <mx:HBox>
               <mx:Label text="author:" fontWeight="bold"/>
               <mx:Label id="author" width="100%"/>
          </mx:HBox>
     </mx:VBox>

</mx:Panel>

PureMVC Mediator:


package com.elad.TDDPureMVC.view
{
     import com.elad.TDDPureMVC.events.UserSelectedFeedEvent;
     import com.elad.TDDPureMVC.model.FeedsPanelViewerProxy;
     import com.elad.TDDPureMVC.model.vo.FeedVO;
     import com.elad.TDDPureMVC.view.components.FeedsPanelViewer;

     import org.puremvc.as3.interfaces.IMediator;
     import org.puremvc.as3.interfaces.INotification;
     import org.puremvc.as3.patterns.mediator.Mediator;

     public class FeedsPanelViewerMediator extends Mediator implements IMediator
     {
          public static const NAME:String = 'FeedsPanelViewerMediator';

          private var feedsPanelViewerProxy:FeedsPanelViewerProxy;

          public function FeedsPanelViewerMediator(viewComponent:Object=null)
          {
               super(NAME, viewComponent);
               feedsPanelViewer.addEventListener(UserSelectedFeedEvent.USERSELECTEDFEED_EVENT, changeSelectedFeed);
          }

          private function changeSelectedFeed(event:UserSelectedFeedEvent):void
          {
               setDetail(event.selectedFeed);
          }

          public function get feedsPanelViewer():FeedsPanelViewer
          {
               return viewComponent as FeedsPanelViewer;
          }

          override public function listNotificationInterests():Array
          {
               return [
                    FeedsPanelViewerProxy.READ_ADOBE_FEEDS_SUCCESS,
                       ];
          }

          private function setDetail(feed:FeedVO):void
          {
               feedsPanelViewer.author.text = feed.author;
               feedsPanelViewer.category.text = feed.category;
               feedsPanelViewer.description.text = feed.description;
               feedsPanelViewer.link.text = feed.link;
               feedsPanelViewer.pubdate.text = feed.pubdate;
          }     

          override public function handleNotification(notification:INotification):void
          {
               feedsPanelViewerProxy = facade.retrieveProxy(FeedsPanelViewerProxy.NAME) as FeedsPanelViewerProxy;

               switch ( notification.getName() )
               {
                    case FeedsPanelViewerProxy.READ_ADOBE_FEEDS_SUCCESS:
                         feedsPanelViewer.feedList.dataProvider = feedsPanelViewerProxy.feedsCollectionVO.collection;
                         setDetail(feedsPanelViewerProxy.selectedFeed);
                         feedsPanelViewer.title = feedsPanelViewerProxy.panelTitle;

                    break;
               }
          }
     }
}

Proxy:


package com.elad.TDDPureMVC.model
{
     import com.elad.TDDPureMVC.model.vo.FeedVO;
     import com.elad.TDDPureMVC.model.vo.FeedsCollectionVO;

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

     import org.puremvc.as3.interfaces.IProxy;
     import org.puremvc.as3.patterns.proxy.Proxy;

     public class FeedsPanelViewerProxy extends Proxy implements IProxy
     {

          public static const NAME:String = "FeedsPanelViewerProxy";
          public static const READ_ADOBE_FEEDS_SUCCESS:String = 'readAdobeFeedsSuccess';
          public static const READ_ADOBE_FEEDS_FAILED:String = 'readAdobeFeedsFailed';
          public var service:HTTPService;

          public function FeedsPanelViewerProxy()
          {
               super(NAME, new FeedsCollectionVO() );

               service = new HTTPService();
            service.url = "http://rss.adobe.com/en/resources_flex.rss";
            service.resultFormat = "e4x";
               service.addEventListener( FaultEvent.FAULT, onFault );
               service.addEventListener( ResultEvent.RESULT, onResult );
          }

          public function getAdobeFeeds():void
          {
               service.send();
          }

          // Cast data object with implicit getter
          public function get feedsCollectionVO():FeedsCollectionVO
          {
               return data.feedsCollectionVO as FeedsCollectionVO;
          }

          public function get selectedFeed():FeedVO
          {
               return (data.feedsCollectionVO as FeedsCollectionVO).collection.getItemAt(0) as FeedVO;
          }

          public function get panelTitle():String
          {
               return data.panelTitle as String;
          }                    

          private function onResult( result:ResultEvent ) : void
          {
               var feed:FeedVO;
               var item:Object;
               var len:int = result.result[0].channel.item.length();
               var collection:FeedsCollectionVO = new FeedsCollectionVO;
               var dataObject:Object = new Object();

               for (var i:int=0; i<len; i++)
               {
                    feed = new FeedVO();
                    item = result.result[0].channel.item[i];

                    feed.author = item.author;
                    feed.category = item.category;
                    feed.description = item.description;
                    feed.link = item.link;
                    feed.pubdate = item.pubdate;
                    feed.title = item.title;

                    collection.addItem(feed);
               }

               dataObject.feedsCollectionVO = collection;
               dataObject.panelTitle = String(result.result.*[0].*[0]);

               setData(dataObject);
               sendNotification(READ_ADOBE_FEEDS_SUCCESS);
          }

          private function onFault( event:FaultEvent) : void
          {
               sendNotification( READ_ADOBE_FEEDS_FAILED, event.fault.faultString );
          }

     }
}

To test the PureMVC framework I will be using puremvc-flexunit-testing. Our test calls the registerObserver method used for listening for a PureMVC notification on a proxy. We pass the PureMVC view, proxy and information for the AddSync such as the method to send a success response and timeout.


package flexUnitTests.proxies
{
     import com.andculture.puremvcflexunittesting.PureMVCNotificationEvent;
     import com.andculture.puremvcflexunittesting.PureMVCTestCase;
     import com.elad.TDDPureMVC.model.FeedsPanelViewerProxy;

     import flexunit.framework.Assert;

     import org.puremvc.as3.core.View;
     import org.puremvc.as3.interfaces.IView;

     public class ReadAdobeFeedsTestCase extends PureMVCTestCase
     {
          override public function setUp():void
          {
               var facade:ApplicationFacade = ApplicationFacade.getInstance();
               facade.registerProxy( new FeedsPanelViewerProxy() );
          }

          private function get proxy():FeedsPanelViewerProxy
          {
               var retVal:FeedsPanelViewerProxy = ApplicationFacade.getInstance().retrieveProxy(FeedsPanelViewerProxy.NAME) as FeedsPanelViewerProxy;
               return retVal;
          }

          private function get view():IView
          {
               return View.getInstance();
          }

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

          public function testReadAdobeFeedsEvent():void
          {
               registerObserver(this.view, this.proxy, FeedsPanelViewerProxy.READ_ADOBE_FEEDS_SUCCESS, handleResponse, 300);
               this.proxy.getAdobeFeeds();
          }

          private function handleResponse(e:PureMVCNotificationEvent):void
          {
               Assert.assertEquals("Feed title is incorrect", proxy.panelTitle, "Flex News");
          }
     }
}
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.

25
Dec

Test Driven Development (TDD) best practices with FlexUnit

FlexUnit moved to Adobe Open Source and it is similar in functionality to JUnit, it allows us to create test suite and test unit. Adobe are showing their commitment to TDD as best practices for dynamic development cycles. In fact, Flex 4 has built-in menu to generate Unit Testing and Test Suite as well as plugin to view results, until Flex 4 is out here’s an example of creating a test suite and test unit using best practices in an easy to understand example.

The TDD process consists of the following steps:

  • Add test
  • Write failed unit test
  • Write code
  • Test Passed
  • Refactor
  • Repeat

First create the scaffolding with the test runner, test suite and test case than start the TDD process listed above. The complete working example can be seen here.

Let’s take a look at the classes:
the test suite holds all the test cases:


/*

 Copyright (c) 2008 Elrom LLC. All Rights Reserved 

 @author   Elad Elrom
 @contact  elad.ny at gmail.com
 @project  Project Name

 @internal 

 */
package flexUnitTests
{
	import flexunit.framework.TestSuite;

	public class TestSuiteClass extends TestSuite
	{
		/**
		 * Class constructor.  If you provide a contstructor in a <code>TestCase</code> subclass,
		 * you should ensure that this constructor is called.
		 *
		 * @param param	The name of the test method to be called in the test run.
		 *
		 */
		public function TestSuiteClass(param:Object=null)
		{
			super(param);
		}

		/**
		 * Holds the test to be run.
		 *
		 * @return new instance of the <code>TestSuite</code>
		 *
		 */
		public static function suite():TestSuite
		{
			var newTestSuite:TestSuite = new TestSuite();
			return newTestSuite;
		}
	}
}

The test case that holds the tests for the logic methods:


/*

 Copyright (c) 2008 Elrom LLC. All Rights Reserved 

 @author   Elad Elrom
 @contact  elad.ny at gmail.com
 @project  Project Name

 @internal 

 */
package flexUnitTests
{
    import com.elad.view.LogicClass;
    import flexunit.framework.TestCase;

    public class TestCaseClass extends TestCase
    {
		/**
		 * A contstructor to pass the method name.
		 *
		 * @param methodName	The name of the test method to be called in the test run.
		 *
		 */
		public function TestCaseClass(methodName:String=null)
		{
			super(methodName);
		}    	

		/**
		 * First test method to do something
		 *
		 */
		public function testFirstMethod():void
		{
		    var logic:LogicClass = new LogicClass();
		    assertEquals( "Expecting zero here", 0, logic.firstMethod() );
		}

		/**
		 * Second test method to do something
		 *
		 */
		public function testNextMethod():void
		{
		    var logic:LogicClass = new LogicClass();
		    assertTrue( "Expecting true here", logic.NextMethod()==50 );
		}
    }
}

And here’s our logic class:


/*

 Copyright (c) 2008 Elrom LLC. All Rights Reserved 

 @author   Elad Elrom
 @contact  elad.ny at gmail.com
 @project  Project Name

 @internal 

 */
package com.elad.view
{
    public class LogicClass
    {
		public function LogicClass()
		{
			// contstructor
        }

       public function firstMethod():Number
       {
             return 0;
       }

       public function NextMethod():Number
       {
       		return 50;
       }
    }
}

Last we need to have a test runner component. Create an instance of the test suite and add all the case unit we would like to run:


<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	xmlns:flexunit="flexunit.flexui.*"
	creationComplete="onCreationComplete()"
	layout="absolute">

	<mx:Script>
		<![CDATA[

			import flexUnitTests.TestCaseClass;
			import flexUnitTests.TestSuiteClass;
			import flexunit.framework.TestSuite;

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

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

				testsToRun.addTest(TestSuiteClass.suite());
				testsToRun.addTest(new TestCaseClass("testFirstMethod"));
				testsToRun.addTest(new TestCaseClass("testNextMethod"));

				return testsToRun;
			}			

		]]>
	</mx:Script>

	<flexunit:TestRunnerBase id="testRunner"/>

</mx:Application>

At this point you can add custom setUp() and tearDown() stubs as well as other test methods and test suites. The complete application can be seen and downloaded (right click ‘view source’), click the image below:

Result

23
Dec

Lightweight Ant scripts for PureMVC following best practices

I am working on a lightweight and easy to use Ant script to generate PureMVC project, that follow best practices such as Test Driven Development (TDD) and possibly implementation of the Presention Model. The following scripts will be generated automatically for PureMVC projects:

1. PureMVC Folder structure.
2. Creation of application facade class.
3. PureMVC Custom creation of commands, proxies and Mediators.
4. FlexUnit custom test unit and test suite classes.
5. Integration of ThunderBolt.
5. ASDOC generator.

I managed to do some of the work, however the work is not completed if you are interested of joining the project and contributing, please let me know.

http://code.google.com/p/puremvc-generator/

20
Dec

AIR 1.5 and Flash Catalyst on mobile devices sample video application in a new book

I have created a demo mobile application with Flex 4 (Gumbo), Flash Catalyst and AIR 1.5 to be deployed on desktops or on a touch screen UMPC as well as future ARM devices that will support AIR 1.5.

The AIR application allows you to search videos from YouTube and download them into your hard drive. Take a look at some screen shots and the application on UMPC mobile device.

Here’s the application in Photoshop:
photoshop

Here’s the application in Flash Catalyst:
Flash Catalyst

State 1:
AIR and Flash Catalyst #2

State 2:

AIR and Flash Catalyst

AIR and Flash Catalyst

I took the pictures from my iphone so the quality is not the best but you get the idea.

AIR and Flash Catalyst on UMPC mobile

AIR and Flash Catalyst on UMPC mobile

The full tutorials and explanation on how to build applications like this one will be available in my new book which will be out in May by APress and can be purchased in a discount price from Amazon.

19
Dec

Presenting AIR in Flex Camp Chicago 2009

I will be presenting at Flex Camp in Chicago on January 22-23, 2009 and covering AIR 1.5 APIs and if time permits, leveraging Flash Catalyst with AIR.

I will briefly cover the following AIR APIs and changes made in AIR 1.5:

1, File I/O API
2. Local storage and Encrypted SQLite
3. Application Update and Notification API
4. Network Awareness
5. Native Windowing API and Chrom Control
6. Building AIR 1.5 application with Flash Catalyst.

Flex Camp

The Flex Camp events are fun and I am excited! I’m looking forward to be presenting. See you there.

14
Dec

Create Flash Catalyst projects for AIR 1.5

Flash Catalyst Alpha version doesn’t support creating an AIR project, however with few tweaks you can still create projects for AIR, and start enjoying the future design/developer development cycle when working on an AIR application.

Import your FXP into Flex Gumbo (Flex 4), which generate a Flex project. Next create a new AIR project and copy the following folders:

1. ApplicationFileName.mxml – contain the application presentation on layer.
2. Components folder – contain the component.
3. Assets folder – contain the images.

Now you can tweak the AIR application file reference the application in your AIR project:

After you compile you still get the following errors:

1. Type ‘BitmapGraphic’ declaration must be contained within the tag
2. Initializer for property ’states’ is not allowed here.

To fix the first issue turn the FxApplication into a Canvas container, group the entire design FXG and move out the states tags and you should be able to compile your application.

The new structure should look like this:


<Canvas xmlns="http://ns.adobe.com/mxml/2009"
       xmlns:d="http://ns.adobe.com/fxg/2008/dt" width="320" height="480"
       backgroundColor="0x000000" xmlns:th="http://ns.adobe.com/thermo/2009">

       <states>
              <State name="page1" th:color="0xcc0000"/>
              <State name="page2" th:color="0x0000"/>
       </states>       

       <Group>
            //  FXG graphic tags
       </Group>
</Canvas>

But wait… we are not done yet.. sometimes when you run your application you will notice that the application is shifted about 10-20 pixel left top. If you try to change the X,Y you will find that the application is still shifted. You can go through your application and shift every graphic top, left properties, which is really not recommended, especially since you probably going to go back and fourth between Flex and Flash Catalyst.

The best approached I found so far is just to add an event listener to “creationComplete” and than shift the X,Y and it worked.


creationComplete="this.y=-26"

I am sure Adobe will take care of these issues and allow the creation of FXP automatically, until then you can use this little method.

13
Dec

Cairngorm and the Presentation Model using Ant scripts

Many theories were written lately about the presentation model. There are many advantage of using the presentation model and you can read about it in Paul William’s blog talking about the different ways to implements the presentation model. You can also download David Deraedt sample application which is called Caringorm application but is not really using the Caringorm framework and he is asking more questions that really have a working example. So I feel that what is really missing is a real POC example. I decided to create a simple example as well as create the custom Ant scripts to automate the process, so we don’t need to keep doing the same thing every time we start a Cairngorm application. This type of Ant script can turn into a framework but I am sure there are still improvement to be made to get it perfected.

The idea is basically to move to a TDD development cycle. TDD is much better than the usual methodology of creating some kind of UML diagrams based on business rules which already includes design patterns. The reason is that business rules keep changing and you may find yourself with a large application and many design patterns to do a simple logic. TDD also forces us to think about every business rule and make sure everything is clear as well as making sure we have what we need and nothing more. TDD also allow us to show results right away.

Using TDD we can generate a simple unit test class, which of course will fail and then we can start implementing the methods until it passes. At this point we don’t care about the code that much but more about passing the test. Once we pass the test and got a green light we can start creating our classes and refactor when needed, for instance replacing “if..else” statements into design patterns when needed.

I believe that working under TDD development cycle can save time in the long run. Once we need to make changes, due to new business rules, we don’t have to worry if the code we added broke previous code and we can just run the test suite to test the entire application.

The example is very simple in nature it loads a module with label field after the services have completed. To view and download the complete example click here.

I believe that using the presentation model is a key in order to create an efficient Test Driven Development (TDD) development cycle. The application logic is separated from the view can be easily tested by unit testing, which can be packages into Unit Suites. The future release of Flex 4 it will be easy to create Unit testing and Test suite classes right from the menu. I am also considering creating the unit testing class automatically to fit Cairngorm presentation model.

The example here is a Caringorm example that is simple in nature and easy to understand. I also updated Cairngen to allow us to a creation of a basic skeleton to implement the presentation model using Ant scripts.

The Ant scripts creates the main entry point to the application “main.mxml” as well as set the model locator to be a locator and not a data holder for the application data.

The new scaffolding will generate the domain, presentation folders under the model package with the following classes:

* LibraryModel.as
* AbstractPM.as
* MainPM.as

I also implemented the flash-ThunderBolt on the commands so it will log in a message every time a command is executed in firebug. You can read about it in my previous blog. Additionally, commands that include delegate should be under Services in command structure so it will be easy to find all the commands related to services.

The ant scripts has issues with Mac OS X since the Mac is missing the Java 1.6 Jars that are needed so I added them under “libs”. So you can easily place them under the “plugin” folder in Eclipse and be up and running quickly.

Feel free to download the updated Cairngen Ant tool with these changes from here.

Please note that the Jars, Flash-ThunderBolt, Cairngorm, Cairngen etc are under copyright and refer to each tool to find out the license agreement.

Let’s create an example. I want to go over with you a simple example that loads a module into our main application, after a service called is made. I simulated a service call by using a timer delay, and I tried to keep this example as simple as possible so you can understand the concept.

Let’s get started. First generate all the scaffolding using the ANT script. Next, we need to create our module which will have a simple label field. I called it FirstModule.mxml


<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="400" height="300">

	<mx:Script>
		<![CDATA[

			import com.elad.application.model.ModelLocator;

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

		]]>
	</mx:Script>

	<mx:Label text="{modelLocator.firstModule.text}" />

</mx:Module>

The text is bindable to the firstModule and notice that it is bindable to the ModelLocator, make sure you add a reference in the model locator.


public static function getInstance() : ModelLocator
{
	if ( instance == null )
	{
		instance = new ModelLocator( new Private() );
		instance.libraryModel = new LibraryModel();
		instance.mainPM = new MainPM( instance.libraryModel );
		instance.firstModule = new FirstModulePM( instance.libraryModel );
	}
	return instance;
}

Notice that in component implementation we could have just passed the presentation model class instance instead of calling the model locator.
In the main.mxml we can now place the module and the rest of the class was generated automatically for you through the Ant script. There is a call to the presentation model pre-initialize and all the logic is done in the presentation model.


<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="absolute"
	xmlns:business="com.elad.application.business.*"
	xmlns:control="com.elad.application.control.*"
	xmlns:view="com.elad.application.view.*"
	preinitialize="modelLocator.mainPM.preinitialize()">

	<mx:Script>
		<![CDATA[

			import com.elad.application.model.ModelLocator;

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

		]]>
	</mx:Script>

	<control:testController />
	<business:Services />

	<mx:ModuleLoader url="{modelLocator.mainPM.MODULE_URL}" />

</mx:Application>

Let’s take a look at the presentation model for the main class. The presentation model was created for you with the Ant script and you need to implement the methods. Take a look at the abstract presentation model, which was also generated automatically for you. I basically took David Deraedt’s class and changes it as well as added more methods since the his abstract class didn’t count for pre-initialized and initialized as a common application will need and other methods that I think are common in real life applications.


/*

 Copyright (c) 2008 Elrom LLC, Inc. All Rights Reserved 

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

 @internal 

 */

package com.elad.application.model.presentation
{
	import flash.events.Event;
	import flash.events.EventDispatcher;

	/**
	 * Abstract class to include the first time the class run as well as the subsequent init of the class
	 * Also included initialize methods and handler to be used in the view.
	 *
	 */	

	public class AbstractPM extends EventDispatcher
	{

		/**
		 *
		 * Define a var to store weather the application state was shown already
		 *
		 */
		private var firstShow:Boolean = true;

		/**
		 *
		 * Default constructor
		 *
		 */
		public function AbstractPM()
		{
		}

		/**
		 * Method to handle first show of the application
		 *
		 */
		public function handleShow():void
		{
			if( firstShow )
			{
				handleFirstShow();
				firstShow = false ;
			}

			else handleSubsequentShows();
		}

		/**
		 * Method to be called on preinitialize
		 *
		 */
		public function preinitialize():void
		{
			handlePreInitialize();
		}

		/**
		 * Method to be called on initialize
		 *
		 */
		public function initialize():void
		{
			handleInitialize();
		}

		/**
		 * Method to handle preinitialize
		 *
		 */
		protected function handlePreInitialize():void
		{
			// to be overriden
		}

		/**
		 * Method to handle initialize of the application
		 *
		 */
		protected function handleInitialize():void
		{
			// to be overriden
		}

		/**
		 * Method to handle the view once preinit and init is completed
		 *
		 */
		protected function handleCompleted():void
		{
			// to be overriden
		}					

		/**
		 * Method to implement first show of the application
		 *
		 */
		protected function handleFirstShow():void
		{
			// to be overriden
		}	

		/**
		 * Method to handle dubsequent shows of the application
		 *
		 */
		protected function handleSubsequentShows():void
		{
			// to be overriden
		}

		/**
		 * Method invoke once the Initialize is completed.
		 *
		 * @param event
		 *
		 */
		public function preInitializeCompletedHandler(event:Event):void
		{
			handleInitialize();
		}

		/**
		 * Method invoke once the Initialize is completed.
		 *
		 * @param event
		 *
		 */
		public function initializeCompletedHandler(event:Event):void
		{
			handleShow();
		}
	}
}

The abstract class is pretty self explanatory, but feel free to ask questions if you are having hard time understanding it. Moving forward to the MainPM we need to implement the methods and add some logic. The handler for the pre initialized listen to the “PreInitializationCommandCompleted” event (which was also created automatically for you), it than call the “StartupServicesEvent” which will make all the service calls, but in our simulated case just use the counter to delay the application. After the startup services are done we can move to the initialize method “handleInitialize”, which listen to the “InitializationCommandCompleted” event and in our case we don’t need to do anything so we just dispatched that event, but in real life application you can use that to apply all kind of logic to your application.

After the event is fire it will be sent to the “handleShow” method in the abstract class, which will decide if it the first time the application is used and sent to the “handleFirstShow” or after that sent to “handleSubsequentShows” method, in our case I wanted to keep it simple so they both just go to the “handleCompleted” method, which set the module URL: MODULE_URL = “FirstModule.swf” and starts the module.


/*

 Copyright (c) 2008 Elrom LLC, Inc. All Rights Reserved 

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

 @internal 

 */

package com.elad.application.model.presentation
{
	import com.adobe.cairngorm.control.CairngormEventDispatcher;
	import com.elad.application.events.InitializationCommandCompleted;
	import com.elad.application.events.PreInitializationCommandCompleted;
	import com.elad.application.events.StartupServicesEvent;
	import com.elad.application.model.ModelLocator;
	import com.elad.application.model.domain.LibraryModel;

	import mx.logging.Log;

	import org.osflash.thunderbolt.ThunderBoltTarget;

    [Bindable]
    /**
     *
     * Defines the <code>MainPM<code> Value Object implementation
     *
     */
	public class MainPM extends AbstractPM
	{

		/**
		 *
		 * Define an instance of <code>ThunderBoltTarget</code>
		 *
		 * @see org.osflash.thunderbolt.ThunderBoltTarget
		 * @see mx.logging.Log
		 *
		 */
		private var _target:ThunderBoltTarget = new ThunderBoltTarget();

		/**
		 *
		 * Define an instance of the <code>PagesPM</code>
		 *
		 */
		var firstModulePM:FirstModulePM;		 

		/**
		 *
		 * Define an instance of the <code>LibraryModel</code>
		 *
		 */
		public var libraryModel:LibraryModel;

		/**
		 *
		 * Define moduale URL for used with Modules
		 *
		 */
		public var MODULE_URL:String = "";		

		/**
		 * Defualt constractor set the <code>LibraryModel</code>
		 * @param LibraryModel
		 *
		 */
		public function MainPM(libraryModel:LibraryModel)
		{
			this.libraryModel = libraryModel;
			firstModulePM = new FirstModulePM(libraryModel);
		}

		/**
		 * Method to handle first show of the application
		 *
		 */
		override protected function handlePreInitialize():void
		{
			// set filter for logging API and inject thunder bolt
			_target.filters = ["com.elad.application.commands.*"];
			Log.addTarget(_target);

		    // track once pre-initialize completed
		    CairngormEventDispatcher.getInstance().addEventListener( PreInitializationCommandCompleted.COMPLETED, preInitializeCompletedHandler );

		    // call startup services
		    new StartupServicesEvent().dispatch();
		}

		/**
		 * Method to handle first show of the application
		 *
		 */
		override protected function handleInitialize():void
		{
			// track once initialize completed
		    CairngormEventDispatcher.getInstance().addEventListener( InitializationCommandCompleted.COMPLETED, initializeCompletedHandler );

		    new InitializationCommandCompleted().dispatch();
		}

		/**
		 * Method to handle first show of the application
		 *
		 */
		override protected function handleFirstShow():void
		{
			// implements or leave default
			handleCompleted();
		}

		/**
		 * Method to handle dubsequent shows of the application
		 *
		 */
		override protected function handleSubsequentShows():void
		{
			// implements or leave default
			handleCompleted();
		}		

		/**
		 * Method to handle the view once preinit and init are completed
		 *
		 */
		override protected function handleCompleted():void
		{
			// remove event listeners
			CairngormEventDispatcher.getInstance().removeEventListener(	PreInitializationCommandCompleted.COMPLETED, preInitializeCompletedHandler );
			CairngormEventDispatcher.getInstance().removeEventListener(	InitializationCommandCompleted.COMPLETED, initializeCompletedHandler );		

			// implements changes in view
			MODULE_URL = "FirstModule.swf";
		}		

	}
}

View and download the complete example from here

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() );
03
Dec

Use ThunderBolt in Cairngorm application to log messages and debug

Here’s a tip regarding logging messages in Cairngorm application. I would recommend using ThunderBolt with Mozilla firebug. ThunderBolt is a light weight logging mechanism and very simple to implement; on every user gesture add a logger message during the following methods: execute, result and fault.

To use ThunderBolt just place the ThunderBolt swc in your lib folder and log a message, for instance:
Logger.error (”Logging two objects: A number typed as int and a string”, myNumber, myString);

ThunderBolt screenshot

Once you compile and run the application you can keep track of all the methods that got executed and save time debugging the application. Additionally, using this method can help you figure out if there are unnecessary double calls to the same commands.
Thunderbolt is available to AS2/3 code as well as AIR and Flex Gumbo: http://code.google.com/p/flash-thunderbolt/

There are two ways to implement, one directly through ThunderBolt such as here:

Example:


/*
 Copyright (c) 2008 Elrom LLC, All Rights Reserved 

 @author   Elad Elrom
 @contact  elad.ny at gmail.com
 @project  Example project

 @internal 

 */

package com.elad.Project.commands.services
{
	import com.adobe.cairngorm.commands.ICommand;
	import com.adobe.cairngorm.control.CairngormEvent;
	import com.elad.Project.business.SequenceDelegate;
	import com.elad.Project.events.SequenceEvent;
	import com.elad.Project.model.ModelLocator;

	import mx.rpc.IResponder;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;

	import org.osflash.thunderbolt.Logger;

    /**
     *
     * Defines the associated <code>ICommand</code> implementation for
     * an "Sequence" use-case.
     *
     * <p>
     * The <code>SequenceCommand</code> is utilized to abstract the
     * handling of a <code>SequenceEvent</code>.
     * </p>
     *
     * @see com.elad.Project.events.SequenceEvent
     * @see com.adobe.cairngorm.commands.ICommand
     *
     */
	public final class SequenceCommand implements ICommand, IResponder
	{

		/**
		 *
		 * Defines a local convenience reference to the application
		 * <code>ModelLocator</code> implementations
		 *
		 */
		private var modelLocator:ModelLocator = ModelLocator.getInstance();

	    /**
	     *
	     * Concrete <code>ICommand</code> implementation which handles
	     * an <code>SequenceEvent</code>.
	     *
	     */
		public function execute(event:CairngormEvent) : void
		{
			Logger.info( "SequenceCommand"+" execute" );
			var evt:SequenceEvent = event as SequenceEvent;
			var delegate:SequenceDelegate = new SequenceDelegate( this );

			delegate.callSomeMethod();
		}

	    /**
	     *
	     * Handles the service result of the <code>SequenceDelegate</code>
	     * service invocation.
	     *
	     * @see mx.rpc.events.ResultEvent
	     *
	     */
		public function result(data:Object) : void
		{
			Logger.info( "SequenceCommand"+" result" );
			var result:ResultEvent = data as ResultEvent;
		}

	    /**
	     *
	     * Handles the service fault of the <code>SequenceDelegate</code>
	     * service invocation.
	     *
	     * @see mx.rpc.events.ResultEvent
	     *
	     */
		public function fault(info:Object) : void
		{
			var fault:FaultEvent = info as FaultEvent;
			Logger.error( "SequenceCommand", fault );
		}
	}
}

A better way to implements, as suggested by Stefan Bistram which is more recommended since your classes don’t really need to know anything about ThunderBolt and you will be able to make changes without changing your entire code is using ThunderBoltTarget. The way it works is that you inject ThunderBolt to the Logging API built into Flex. You just set the target object in your entry class in MXML;


/**
 *
 * Define an instance of <code>ThunderBoltTarget</code>
 *
 * @see org.osflash.thunderbolt.ThunderBoltTarget
 * @see mx.logging.Log
 *
 */
private var _target: ThunderBoltTarget = new ThunderBoltTarget();

Than on set the filter to point to your command package;


_target.filters = ["com.elad.project.commands.*"];
Log.addTarget(_target);	

And now you can use the Flex Log API to inject your messages;


/*
 Copyright (c) 2008 Elrom LLC, All Rights Reserved 

 @author   Elad Elrom
 @contact  elad.ny at gmail.com
 @project  Example project

 @internal 

 */

package com.elad.Project.commands.services
{
	import com.adobe.cairngorm.commands.ICommand;
	import com.adobe.cairngorm.control.CairngormEvent;
	import com.elad.Project.business.SequenceDelegate;
	import com.elad.Project.events.SequenceEvent;
	import com.elad.Project.model.ModelLocator;

	import mx.rpc.IResponder;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;

    /**
     *
     * Defines the associated <code>ICommand</code> implementation for
     * an "Sequence" use-case.
     *
     * <p>
     * The <code>SequenceCommand</code> is utilized to abstract the
     * handling of a <code>SequenceEvent</code>.
     * </p>
     *
     * @see com.elad.Project.events.SequenceEvent
     * @see com.adobe.cairngorm.commands.ICommand
     *
     */
	public final class SequenceCommand implements ICommand, IResponder
	{

		/**
		 *
		 * Defines a local convenience reference to the application
		 * <code>ModelLocator</code> implementations
		 *
		 */
		private var modelLocator:ModelLocator = ModelLocator.getInstance();

	    /**
	     *
	     * Concrete <code>ICommand</code> implementation which handles
	     * an <code>SequenceEvent</code>.
	     *
	     */
		public function execute(event:CairngormEvent) : void
		{
                  			Log.getLogger("com.elad.project.commands.services.SequenceCommand").info("execute");

			var evt:SequenceEvent = event as SequenceEvent;
			var delegate:SequenceDelegate = new SequenceDelegate( this );

			delegate.callSomeMethod();
		}

	    /**
	     *
	     * Handles the service result of the <code>SequenceDelegate</code>
	     * service invocation.
	     *
	     * @see mx.rpc.events.ResultEvent
	     *
	     */
		public function result(data:Object) : void
		{
			Log.getLogger("com.elad.project.commands.services.SequenceCommand").info("result");

			var result:ResultEvent = data as ResultEvent;
		}

	    /**
	     *
	     * Handles the service fault of the <code>SequenceDelegate</code>
	     * service invocation.
	     *
	     * @see mx.rpc.events.ResultEvent
	     *
	     */
		public function fault(info:Object) : void
		{
			var fault:FaultEvent = info as FaultEvent;
			Log.getLogger("com.elad.project.commands.services.SequenceCommand").error("fault", fault);

		}
	}
}

If you are using Cairngen 2.1.1 I have created template that you can just paste into your library for Cairngorm 2.2.1. Feel free to download and use them. Here’s the one for CommandExcludeDelegate.tpl:


@copy@

package @namespace@.commands
{
	import com.adobe.cairngorm.commands.ICommand;
	import com.adobe.cairngorm.control.CairngormEvent;
	import @namespace@.events.@sequence@Event;
	import @namespace@.model.ModelLocator;
	import mx.logging.Log;

    /**
     *
     * Defines the associated <code>ICommand</code> implementation for
     * the "@sequence@" use-case.
     *
     * <p>
     * The <code>@sequence@Command</code> is utilized to abstract the
     * handling of an <code>@sequence@Event</code>
     * </p>
     *
     * @see @namespace@.events.@sequence@Event
     * @see com.adobe.cairngorm.commands.ICommand
     *
     */
	public final class @sequence@Command implements ICommand
	{
		/**
		 *
		 * Defines a local convenience reference to the application
		 * <code>ModelLocator</code> implementations
		 *
		 */
		private var modelLocator:ModelLocator = ModelLocator.getInstance();

	    /**
	     *
	     * <code>ICommand</code> implementation which handles an
	     * <code>@sequence@Event</code>.
	     *
	     * <p>
         * The <code>@sequence@Command</code> does not require a specific
         * service invocation to be made, therefore the handling of an
         * <code>@sequence@Event</code> is completely managed by the
         * <code>@sequence@Command</code>.
	     * </p>
	     *
	     */
		public function execute(event:CairngormEvent) : void
		{
			Log.getLogger("@namespace@.commands.@sequence@Command").info("execute");
			var evt:@sequence@Event = event as @sequence@Event;
		}
	}
}

And here’s the one for CommandIncludeDelegate.tpl:


@copy@

package @namespace@.commands
{
	import com.adobe.cairngorm.commands.ICommand;
	import com.adobe.cairngorm.control.CairngormEvent;
	import @namespace@.business.@sequence@Delegate;
	import @namespace@.events.@sequence@Event;
	import @namespace@.model.ModelLocator;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.AsyncToken;
	import mx.rpc.IResponder;
	import mx.logging.Log;

    /**
     *
     * Defines the associated <code>ICommand</code> implementation for
     * an "@sequence@" use-case.
     *
     * <p>
     * The <code>@sequence@Command</code> is utilized to abstract the
     * handling of a <code>@sequence@Event</code>.
     * </p>
     *
     * @see @namespace@.events.@sequence@Event
     * @see com.adobe.cairngorm.commands.ICommand
     *
     */
	public final class @sequence@Command implements ICommand, IResponder
	{
		/**
		 *
		 * Defines a local convenience reference to the application
		 * <code>ModelLocator</code> implementations
		 *
		 */
		private var modelLocator:ModelLocator = ModelLocator.getInstance();

	    /**
	     *
	     * Concrete <code>ICommand</code> implementation which handles
	     * an <code>@sequence@Event</code>.
	     *
	     */
		public function execute(event:CairngormEvent) : void
		{
			Log.getLogger("@namespace@.commands.@sequence@Command").info("execute");

			var evt:@sequence@Event = event as @sequence@Event;
			var delegate:@sequence@Delegate = new @sequence@Delegate( this );
		}

	    /**
	     *
	     * Handles the service result of the <code>@sequence@Delegate</code>
	     * service invocation.
	     *
	     * @see mx.rpc.events.ResultEvent
	     *
	     */
		public function result(data:Object) : void
		{
			Log.getLogger("@namespace@.commands.@sequence@Command").info("result");
			var result:ResultEvent = data as ResultEvent;
		}

	    /**
	     *
	     * Handles the service fault of the <code>@sequence@Delegate</code>
	     * service invocation.
	     *
	     * @see mx.rpc.events.ResultEvent
	     *
	     */
		public function fault(info:Object) : void
		{
			var fault:FaultEvent = info as FaultEvent;
			Log.getLogger("@namespace@.commands.@sequence@Command")..error("fault", fault);
		}
	}
}