Archive for the 'Flex APIs' Category

16
Jul

Easy way to store event listener references & prevent memory leaks

Many times when we find memory leaks in our applications, it’s due to listeners that have been set by us or someone else and never removed from memory. Although it’s recommended to use weak reference when setting event listeners, the fact that we have to put these three extra parameters often cause developers not to set them, which blocks the garbage collector from collecting these objects:


eventDispatcherObject.addEventListener(type,handler,false,0,true);

The solution is simple. All display objects such as Sprite, UIComponent, Spark etc are all extending event dispatcher class and if you set a line break point and look in the debugger you can see that any display object that the event listeners were set are actually stored in an array.

screen-shot-2010-07-15-at-105913-pm

The only problem is that the array is set to private, which prevents us from cleaning an object from it’s listeners.

See EventDispatcher class:


private function get listeners() : Array; 

Yesterday, I placed a CR in JIRA and recommended changing the access modifier in the EventDispatcher class to help prevent memory leaks and increase performance (please vote if you agree):
http://bugs.adobe.com/jira/browse/ASC-4104

I also recommended considering adding a method to EventDispatcher called clearAllListeners(); to allow removing any event listeners that have been created on an object. I am not sure it if makes total sense, since it may add an overhead to any class that extends EventDispatcher. However, having access to listeners() can allow us to remove all listeners, when needed.

With that said, even if Adobe’s SDK team agrees to make the change I highly doubt it if EventDispatcher will end up changing quickly, since it’s part of the core libraries: playerglobal.swc and these are not changing often.

Meanwhile, I created a quick solution to help out with keeping tabs of event listeners added to an object in an easy way.

The idea is to make it simple enough and lightweight so that it will be useful without the need to write any code to create the class or initialize a collection object (such as Array or a Dictionary). Also this class doesn’t have to be used at all times, but be there when you need it and when you have cases where you suspect you’re dealing with a memory leak that is related to an event listener that hasn’t been removed.

Implementation

I will start from the implementation, just to show how easy it is to use. We set an event just like before with one change. We hold a reference to the type and the handler while setting the listener.


movieClip.addEventListener( listeners.type = MouseEvent.CLICK, listeners.handler = onClick );
movieClip.addEventListener( listeners.type = MouseEvent.DOUBLE_CLICK, listeners.handler = onDoubleClick );

As you can see we just set the listeners.type and listeners.handler and it automatically creates a collection that stores references for you, while you add the event listener and then when you want to remove the listeners you just call the following method:


listeners.removeAllListeners( movieClip );

No need to do anything else… that’s it - it’s that simple!

API

Although it’s unconventional I set the class name (listeners) as lower case on purpose, so when you implement it will appear as if it’s part of your class when using the API.


package com.elad.optimize.memory
{
	import flash.events.IEventDispatcher;

	public class listeners
	{
		private static var listenerItem:ListenerItem = null;
		private static var listenerItems:Vector.<ListenerItem>;

		public static function set type( listenerType:String ):void
		{
			listenerItem = new ListenerItem;
			listenerItem.type = listenerType;
		}

		public static function set handler( eventHandlerReference:Function ):void
		{
			if (listenerItem == null)
				return;

			listenerItem.handler = eventHandlerReference;

			if ( listenerItems == null )
				listenerItems = new Vector.<ListenerItem>();

			listenerItems.push( listenerItem );
			listenerItem = null;
		}

		public static function removeAllListeners(eventDispatcherObject:IEventDispatcher, clearListenerItems:Boolean = true ):void
		{
			if ( listenerItems == null )
				return;

			var newListenerItems:Vector.<ListenerItem> = new Vector.<ListenerItem>();

			listenerItems.forEach( function callback(item:ListenerItem, index:int, vector:Vector.<ListenerItem>):void {

				if (eventDispatcherObject.hasEventListener( item.type ) )
				{
					eventDispatcherObject.removeEventListener( item.type, item.handler );

					if ( eventDispatcherObject.willTrigger( item.type ) )
						newListenerItems.push( item );
				}
				else
				{
					newListenerItems.push( item );
				}
			});

			if (clearListenerItems)
				listenerItems = null;
			else
				listenerItems = newListenerItems;
		}
	}
}

class ListenerItem {
	public var type:String;
	public var handler:Function;
}

Each time you set the type a new ListenerItem is created to hold the the type and handler.


		public static function set type( listenerType:String ):void
		{
			listenerItem = new ListenerItem;
			listenerItem.type = listenerType;
		}

Once both type and handler are set we can add them to the listenerItems collection.


		public static function set handler( eventHandlerReference:Function ):void
		{
			if (listenerItem == null)
				return;

			listenerItem.handler = eventHandlerReference;

			if ( listenerItems == null )
				listenerItems = new Vector.<ListenerItem>();

			listenerItems.push( listenerItem );
			listenerItem = null;
		}

When you’re ready to remove all the listeners you can use removeAllListeners method. There may be cases where you want to store the events added on more than one object so when it’s time to clean you can use clearListenerItems set to false, so it will keep the collection of handlers and we will be able to clean other objects.


		public static function removeAllListeners(eventDispatcherObject:IEventDispatcher, clearListenerItems:Boolean = true ):void
		{
			if ( listenerItems == null )
				return;

			var newListenerItems:Vector.<ListenerItem> = new Vector.<ListenerItem>();

			listenerItems.forEach( function callback(item:ListenerItem, index:int, vector:Vector.<ListenerItem>):void {

				if (eventDispatcherObject.hasEventListener( item.type ) )
				{
					eventDispatcherObject.removeEventListener( item.type, item.handler );

					if ( eventDispatcherObject.willTrigger( item.type ) )
						newListenerItems.push( item );
				}
				else
				{
					newListenerItems.push( item );
				}
			});

			if (clearListenerItems)
				listenerItems = null;
			else
				listenerItems = newListenerItems;
		}

Example

Using the API can help when you set an anonymous handler. It’s not recommended to set a weak reference to an anonymous handler, since the listener may be removed next time the garbage collector will do a round and it will appear as if our method sometimes is working and sometimes isn’t.

What I usually do is use argument.callee


event.currentTarget.removeEventListener(event.type, arguments.callee);

movieClip.addEventListener( MouseEvent.DOUBLE_CLICK, function(event:*):void {
					event.currentTarget.removeEventListener(event.type, arguments.callee);
				});

This will remove the listener after the first time the handler got called. However, there are cases when you want to use anonymous method and remove the listener later on. Using the API you can do that by just storing a reference to the method, just as we have done before:


movieClip.addEventListener( listeners.type = MouseEvent.DOUBLE_CLICK, listeners.handler = function(event:*):void {
					trace("DOUBLE_CLICK");
				});

Now let’s look at the complete example. We set two movie clips with event listeners and then each button allows us to remove a listener from each one of the movie clips. Check method allows us to check the status of the object and see if that event is still set on that object or not.

screen-shot-2010-07-15-at-105941-pm


<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/mx"
			   minWidth="955" minHeight="600"
			   creationComplete="check()">
	<fx:Script>
		<![CDATA[
			import com.elad.optimize.memory.listeners;

			private var movieClip:MovieClip = new MovieClip();
			private var movieClip2:MovieClip = new MovieClip();

			protected function btn_clickHandler(event:MouseEvent):void
			{
				movieClip.addEventListener( listeners.type = MouseEvent.CLICK, listeners.handler = onClick );
				movieClip2.addEventListener( listeners.type = MouseEvent.CLICK, listeners.handler = onClick );

				movieClip.addEventListener( listeners.type = MouseEvent.DOUBLE_CLICK, listeners.handler = function(event:*):void {
					trace("DOUBLE_CLICK");
				});

				movieClip2.addEventListener( listeners.type = MouseEvent.DOUBLE_CLICK, listeners.handler = function(event:*):void {
					trace("DOUBLE_CLICK");
				});				

				check();
			}			

			private function onClick(event:MouseEvent):void
			{
				trace("onClick");
			}

			private function check():void
			{
				trace("hasEventListener movieClip CLICK: " + movieClip.hasEventListener( MouseEvent.CLICK ) );
				trace("hasEventListener movieClip2 CLICK: " + movieClip2.hasEventListener( MouseEvent.CLICK ) );
				trace("hasEventListener movieClip DOUBLE_CLICK: " + movieClip.hasEventListener( MouseEvent.DOUBLE_CLICK ) );
				trace("hasEventListener movieClip2 DOUBLE_CLICK: " + movieClip2.hasEventListener( MouseEvent.DOUBLE_CLICK ) );
				trace("------------------------------------");
			}

		]]>
	</fx:Script>

	<s:Button id="btn" label="Add event listeners"
			  click="btn_clickHandler(event)"/>
	<s:Button label="remove movieClip listeners" x="158" y="0"
			  click="listeners.removeAllListeners( movieClip, false ); check();" width="171"/>

	<s:Button label="remove movieClip2 listeners" x="158" y="29"
			  click="listeners.removeAllListeners( movieClip2, false ); check();"/>	

</s:Application>

Console results:


application complete:

hasEventListener movieClip CLICK: false
hasEventListener movieClip2 CLICK: false
hasEventListener movieClip DOUBLE_CLICK: false
hasEventListener movieClip2 DOUBLE_CLICK: false
------------------------------------

Once we click to add the event listeners:

hasEventListener movieClip CLICK: true
hasEventListener movieClip2 CLICK: true
hasEventListener movieClip DOUBLE_CLICK: true
hasEventListener movieClip2 DOUBLE_CLICK: true
------------------------------------

Once we clear the movie clip object:

hasEventListener movieClip CLICK: false
hasEventListener movieClip2 CLICK: true
hasEventListener movieClip DOUBLE_CLICK: false
hasEventListener movieClip2 DOUBLE_CLICK: true
------------------------------------

Once we clear the second movie clip object:

hasEventListener movieClip CLICK: false
hasEventListener movieClip2 CLICK: false
hasEventListener movieClip DOUBLE_CLICK: false
hasEventListener movieClip2 DOUBLE_CLICK: false
------------------------------------

Project:
http://github.com/EladElrom/Flash-Optimizing-Tools
http://github.com/EladElrom/Flash-Optimizing-Tools/blob/master/src/com/elad/optimize/memory/listeners.as

Cheers :)

04
Jul

API to control player’s frame rate per second on Pure AS3/Flex/AIR apps

One of the most effective and easy ways to optimize your Flash application is controlling the player’s frame rate per second (fps). In fact, FP 10.1, which takes mobility into account, have optimized performance by adjusting the frame rate. FP 10.1 also help manage the SWFs that are loaded and delay loading SWFs based on priority, visibility as well as device memory and CPU resources.

Similarly, one of the biggest complaints for AIR is that apps consume too much CPU & memory and have large runtime sizes. Adobe has addressed some of the complaints and in fact in AIR 2.0 Adobe’s team put effort into increasing optimization and decreasing resources used by the AIR application by providing file improvement with smaller runtime size and less CPU/memory usage.

Many have spoken about the performance issues with AIR in regards to keeping the FPS low and in fact having our application obeying citizens in times where the Flash platform is criticized as being slow and inefficient is important.

I believe that it is partially our responsibility, as developers, to ensure our apps are obeying citizens and don’t consume too many resources.

With that said, for some reason, I haven’t seen an implementation that takes into account the three Flash platform pure AS3/ Flex and AIR and decided to create a small API to accommodate for that.

Consider the following. A user has a browser open with 5-10 tabs and each tab has about 2-3 swfs. Ideally, when you don’t use a swf it will reduce the memory footprint. How about few AIR apps and web browsers all running at the same time?

The idea is to have one utility class to help you control and adjust your fps and take into account the following:

  • Reducing fps when your app is inactive
  • Increase the fps once the app is active again
  • Increase fps while animation is playing to create a more smooth experience and keeping a stack of all the animations being played to know when we can drop the fps.
  • Provide a cross platform API (Pure AS3, Flex, AIR)

In the bigger picture I have started an ‘optimization tools’ project (see here). This project will provide tools that can help optimize a Flash application. FrameRateControl is just the starting point and the idea is to eventually create a manager that will provide helpful tools for a developer all bundled together into one small swc.

API explanation

Let’s look at the API.

Default constructor allows passing the configuration params that will be used in the API. For instance, setting whether it is debug mode and we will have trace statements indicating the fps, flag whether it’s an AIR app, the active, animation and sleep fps and call back method.

Notice that the main application is being passed instead since Flex, AIR and pure AS 3 holds the main application differently.

  • Flex 4 holds the main app here: FlexGlobals.topLevelApplication
  • Pure AS 3 holds the main app as the Sprite so it’s this
  • AIR holds the main app here: this when called from the main window.

public function FrameRateControl( main:*, isDebugMode:Boolean = false,
											isAIR:Boolean = false,
											sleepFramerate:int = 4,
											activeFramerate:int = 25,
											animationFramerate:int = 50,
											sleepModeCallback:Function = null,
											activeModeCallback:Function = null )

In this constructer we set parameters and set events to listen based on the type of app we're running.

<p>[sourcecode language='css']
			this.main = main;
			this.isAIR = isAIR;

			SLEEP_MODE_FRAME_RATE = sleepFramerate;
			ACTIVE_MODE_FRAME_RATE = activeFramerate;
			ANIMATION_FRAME_RATE = animationFramerate;

			this.sleepModeCallback = sleepModeCallback;
			this.activeModeCallback = activeModeCallback;

			this.isDebugMode = isDebugMode;

			if ( isAIR )
			{
				main.addEventListener( Event.DEACTIVATE, sleepModeHandler, false, 0, true );
				main.addEventListener( Event.ACTIVATE, activeModeHandler, false, 0, true );
			}
			else
			{
				main.addEventListener( MouseEvent.MOUSE_OUT, sleepModeHandler, false, 0, true );
			}

The setFrameRate method sets the frame rate on the main window. In case the user is in debug mode we want to displace trace statement in the console to show the change.


		public function setFrameRate(framerate:int):void
		{
			main.stage.frameRate = framerate;

			if ( isDebugMode )
				trace("info :: new framerate was set: " + framerate );
		}

Once the app goes into sleep mode the sleepModeHandler method is called and allows us to handle a few cases. In case this is a Flex/Pure AS app, the sleep mode gets dispatched from mouse event so we want to check whether there is a related object assign. In case there isn’t any related object assign, we know that the user has left the stage.

We then want to set an event to listen to when the user is back. AIR needs the ACTIVATE event constant name, while Flex/AS3 needs the MOUSE_MOVE constant.


		private function sleepModeHandler(event:*):void
		{
			if ( (event as Object).hasOwnProperty("relatedObject")
			 && event.relatedObject != null)
				return;

			if ( isAIR )
			{
				main.removeEventListener( Event.DEACTIVATE, sleepModeHandler );
				main.addEventListener( Event.ACTIVATE, activeModeHandler, false, 0, true );
			}
			else
			{
				main.removeEventListener( MouseEvent.MOUSE_OUT, sleepModeHandler );
				main.addEventListener( MouseEvent.MOUSE_MOVE, activeModeHandler, false, 0, true );
			}

			isSleepMode = true;
			setFrameRate( SLEEP_MODE_FRAME_RATE );

			if ( sleepModeCallback != null )
				sleepModeCallback();
		}

Once the app is in sleep mode it will wait for the event that indicates that the app is active again and will call activeModeHandler. In case this is an AIR app it will wait for the app to go into sleep mode again using Event.DEACTIVATE constant otherwise we will be using the MouseEvent.MOUSE_OUT event constant. We are also going to dispatch the call back function in case it was set.


		private function activeModeHandler(event:*):void
		{
			if ( isAIR )
			{
				main.addEventListener( Event.DEACTIVATE, sleepModeHandler );
				main.removeEventListener( Event.ACTIVATE, activeModeHandler );
			}
			else
			{
				main.addEventListener( MouseEvent.MOUSE_OUT, sleepModeHandler );
				main.removeEventListener( MouseEvent.MOUSE_MOVE, activeModeHandler );
			}

			isSleepMode = false;
			setFrameRate( ACTIVE_MODE_FRAME_RATE );

			if ( activeModeCallback != null )
				activeModeCallback();
		}

Once we are running an animation we may want to run it in a higher fps for a better user experience. The API by default set the fps for animation at 50 fps, but you can specify that when you set the constructer. Notice that we are using a dictionary to hold the key names for easy access.


		public function animate( animationNameKey:String ):void
		{
			animationsNamesMap[animationNameKey] = animationNameKey;

			setFrameRate( ANIMATION_FRAME_RATE );
		}

Once an animation is complete, we will delete the key from the dictionary and in case there isn’t any keys (meaning no more animation to consider) we will set the stage frame rate to active mode.


		public function clearAnimation( animationNameKey:String ):void
		{
			delete animationsNamesMap[animationNameKey];

			for (var key:* in animationsNamesMap)
			{
				return;
			}

			setFrameRate( ACTIVE_MODE_FRAME_RATE );
		}

clearAllAnimationKeys method is used to remove all animation names from the dictionary. So in case we want to start over or ensure the collection of names is empty we can use this method.


		public function clearAllAnimationKeys( useWeakReference:Boolean = true ):void
		{
			animationsNamesMap = new Dictionary( useWeakReference );
		}

The frameRate getter allows us to get the current fps in our application.


		public function get frameRate():int
		{
			return main.stage.frameRate;
		}

Implementation

No let’s take a look at a Flex 4 implementation. Once we have creation complete event we add Mr. doob stats to monitor fms and memory usage.

We set the API passing the main application instance:

frameRateControl = new FrameRateControl( FlexGlobals.topLevelApplication, true, true );


<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/mx"
			   minWidth="955" minHeight="600"
			   creationComplete="creationCompleteHandler()">
	<fx:Script>
		<![CDATA[
			import com.elad.optimize.framerate.FrameRateControl;

			import mx.core.FlexGlobals;
			import mx.core.UIComponent;

			private var frameRateControl:FrameRateControl;

			protected function creationCompleteHandler():void
			{
				var statsSprite:Stats = new Stats();
				var componenent:UIComponent = new UIComponent();

				componenent.addChild( statsSprite );
				stats.addElement( componenent );

				frameRateControl = new FrameRateControl( FlexGlobals.topLevelApplication, true );
			}

		]]>
	</fx:Script>

	<s:Group id="stats" />

I am setting two rotation animations to show you how to use the animation feature. One animation is shorter and one is longer.


	<fx:Declarations>
		<s:Rotate3D id="rotateAnimation" target="{rotateBtn}"
					angleYFrom="0" angleYTo="360"
					duration="10000"
					effectStart="frameRateControl.animate('rotateAnimation')"
					effectEnd="frameRateControl.clearAnimation('rotateAnimation')"/>

		<s:Rotate3D id="rotateAnimation2" target="{rotateBtn2}"
					angleYFrom="0" angleYTo="360"
					duration="3000"
					effectStart="frameRateControl.animate('rotateAnimation2')"
					effectEnd="frameRateControl.clearAnimation('rotateAnimation2')"/>
	</fx:Declarations>

Once you click the long rotate button you can see the fps rate goes up and you can also run the short animation and although the short animation is completed the fps will stay at 50 until the longer animation complete as well.

screen-shot-2010-07-04-at-93958-am


	<s:Button id="rotateBtn" label="long rotate" click="rotateAnimation.play();" width="200" x="40" y="314"/>
	<s:Button id="rotateBtn2" label="rotate" click="rotateAnimation2.play();" width="200" x="40" y="350"/>

</s:Application>

Test the application functionality by leaving the leaving the app and noticing that the fps drops to 4 fps.

To run the app on pure AS3 projects - initialize the API with “this” as the main instance:


var framerateManager:FrameRateOptimizer = new FrameRateOptimizer( this, true );

In AIR app just pass “true” for the third param, which will indicate that it’s an AIR app:


new FrameRateOptimizer( this, true, true );

SWC:
http://github.com/EladElrom/Flash-Optimizing-Tools/tree/master/swc/

ASDOC:
http://github.com/EladElrom/Flash-Optimizing-Tools/tree/master/asdoc/

Project:
http://github.com/EladElrom/Flash-Optimizing-Tools

14
May

Utility class to help reading/writing files in Flash 10

Before the release of Flash 10 we needed to use some sort of a server side proxy or Javascript in order to read or write file in the user’s system. We would send the request to a proxy, which will handle the request and send it back to Flash once completed.

Flash Player 10 has exposed two new methods in FileReference: load and save.

The new methods allow you to read and write data right into the user’s local system. You get information about the files such as modify date, creator, size and other properties, however unlike AIR’s FileStream API the location of the files will not be visible to us and we can only do asynchronous calls.

Asynchronous operations perform operations in the background without waiting for the operation to complete, long processes such as uploading or downloading a large file will not impact the application and the user can keep using the application. Synchronous operations on the other hand, the operation is waiting for the original operation to complete before allowing the user to do any interactions with the application.

Recently, Adobe have closed a gap and forced the load and save methods to be used follow user interaction, such as clicking a button, so the user will be aware of being asked to save or load and not see a browse window comes out of nowhere.

The process of reading and writing files is easy, however I created a helper to make the process of reading and writing files even easier. The class called: LocalFileHelper.as and it uses the FileReference API.

Let’s take a look at the code that shows a simple implementation of loading and saving a file:


<FxApplication xmlns="http://ns.adobe.com/mxml/2009"
	minWidth="1024"
	minHeight="768" initialize="initializeHandler(event)">

	     <Script>
          <![CDATA[
	     		import com.elad.framework.utils.events.LocalFileErrorEvent;
	     		import com.elad.framework.utils.events.LocalFileLoadedEvent;
	     		import mx.controls.Alert;
	     		import com.elad.framework.utils.events.LocalFileEvent;
	     		import com.elad.framework.utils.enum.FileTypeFormat;
	     		import com.elad.framework.utils.LocalFileHelper;
	     		import mx.events.FlexEvent;

	     		private var localFileHelper:LocalFileHelper;

	     		protected function initializeHandler(event:FlexEvent):void
	     		{
	     			localFileHelper = new LocalFileHelper( FileTypeFormat.FILE_FILTER_TEXT_TYPE );

	     			localFileHelper.addEventListener(LocalFileEvent.FILE_LOAD_BROWSE, onFileSelect);
	     			localFileHelper.addEventListener(LocalFileEvent.FILE_SAVE_BROWSE, function():void { trace("Save browse complete"); } );
	     			localFileHelper.addEventListener(LocalFileEvent.FILE_SAVE_SUCCESSFULLY, function():void { trace("Save complete!"); } );
	     			localFileHelper.addEventListener(LocalFileEvent.FILE_CANCEL, function():void { Alert.show("Cancel"); } );
	     			localFileHelper.addEventListener(LocalFileErrorEvent.FILE_ERROR, function():void { trace("file error") } );
	     		}

	     		private function loadFile():void
	     		{
	     			localFileHelper.browse();
	     		}

				private function onFileSelect(event:LocalFileEvent):void
				{
					localFileHelper.addEventListener(LocalFileLoadedEvent.DATA_LOADED, onDataLoaded );
					localFileHelper.load();
				}

				private function onDataLoaded(event:LocalFileLoadedEvent):void
				{
					output.text = LocalFileHelper.convertByteArrayToText( event.byteLoaded );
				}

				private function saveFile():void
				{
					localFileHelper.save( output.text, "test.txt" );
				} 		

          ]]>
     </Script>

     <FxButton label="Load" click="loadFile()" />
     <FxButton label="Save" click="saveFile()"  x="84"/>
     <TextArea id="output" y="37" width="397" height="327"/>

</FxApplication>

Take a look at the utility class:


package com.elad.framework.utils
{
	import com.elad.framework.utils.enum.FileTypeFormat;
	import com.elad.framework.utils.enum.InteractionStates;
	import com.elad.framework.utils.events.LocalFileErrorEvent;
	import com.elad.framework.utils.events.LocalFileEvent;
	import com.elad.framework.utils.events.LocalFileLoadedEvent;

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.net.FileReference;
	import flash.utils.ByteArray;

	public class LocalFileHelper extends EventDispatcher
	{

	    //--------------------------------------------------------------------------
	    //
	    //  Variables
	    //
	    //--------------------------------------------------------------------------

		/**
		 * @private
		 *
		 */
		private var fileType:Array;

		/**
		 * @private
		 *
		 */
		private var fileReference:FileReference;

		/**
		 * @private
		 *
		 */
		private var interactionState:String;

	    //--------------------------------------------------------------------------
	    //
	    //  Constructor
	    //
	    //--------------------------------------------------------------------------

		/**
		 * Default constructor, which call the <code>reset</code> method to set the file reference and type.
		 *
		 * @param fileType	you can set the type of files you want to use.  The file types are listed in <code>FileTypeFormat</code>
		 * @see	com.elad.framework.utils.enum.FileTypeFormat
		 *
		 */
		public function LocalFileHelper(fileType:Array=null)
		{
			reset(fileType);
		}

		/**
		 * Reset method will allow the implementation to reset the file reference and set again the file type filter.
		 * The default file type is <code>FileTypeFormat.FILE_FILTER_ALL_FILES_TYPE</code> which will allow selecting
		 * any file type.
		 *
		 * @param fileType	you can set the type of files you want to use.  The file types are listed in <code>FileTypeFormat</code>
		 * @see	com.elad.framework.utils.enum.FileTypeFormat
		 *
		 */
		public function reset(fileType:Array=null):void
		{
			if (fileType == null)
			{
				fileType = FileTypeFormat.FILE_FILTER_ALL_FILES_TYPE;
			}

			this.fileType = fileType;
			fileReference = new FileReference();
		}

		/**
		 * The browse method is useful when you need to load a file, you first browse for the file and than you load the file.
		 * When you browse for the file the file type that was selected is used.
		 *
		 */
		public function browse():void
		{
			this.interactionState = InteractionStates.BROWSE;

			fileReference.addEventListener(Event.SELECT, onFileSelectEventHandler);
            fileReference.addEventListener(Event.CANCEL, onFileCancelEventHandler);

			fileReference.browse(fileType);
		}

		/**
		 * Static method to convert the <code>ByteArray</code> data into a string.
		 *
		 * @example
		 * <listing version="3.0">
		 * 	var text:String = LocalFileHelper.convertByteArrayToText( event.byteLoaded );
		 * </listing>
		 *
		 * @param data
		 * @return
		 *
		 */
		public static function convertByteArrayToText(data:ByteArray):String
		{
			var retVal:String;
			retVal = data.readUTFBytes(data.bytesAvailable);

			return retVal;
		}

		/**
		 * Method to load a file.  The method add event listeners and use the <code>fileReference.load</code>
		 * method to do the loading.  To load a file you must first call the <code>browse</code> method.
		 *
		 */
		public function load():void
		{
			if (this.interactionState != InteractionStates.BROWSE)
			{
				this.dispatchEvent( new LocalFileErrorEvent( "You must browse before trying to load a file!" ) );
			}

			this.interactionState = InteractionStates.LOAD_FILE;

            fileReference.addEventListener(Event.COMPLETE, onCompleteEventHandler);
            fileReference.addEventListener(IOErrorEvent.IO_ERROR, onIOErrorEventHandler);

            fileReference.load();
		}

		/**
		 * Save method can be used to save data into a file.  The method must follow a user interaction.
		 *
		 * @param data	Any type of data
		 * @param fileName	You can set the file name, ie: "test.txt"
		 *
		 */
		public function save(data:*, fileName:String):void
		{
			this.interactionState = InteractionStates.SAVE_FILE;

			fileReference.addEventListener(Event.COMPLETE, onCompleteEventHandler);
			fileReference.addEventListener(IOErrorEvent.IO_ERROR, onIOErrorEventHandler);

			fileReference.addEventListener(Event.SELECT, onFileSelectEventHandler);
			fileReference.addEventListener(Event.CANCEL, onFileCancelEventHandler);

			fileReference.save(data, fileName);
		}

	    //--------------------------------------------------------------------------
	    //
	    //  Event handlers
	    //
	    //--------------------------------------------------------------------------

		/**
		 * Method to handle the two cases where the browse gets calls:
		 *
		 * <ul>
		 * 	<li>Once you browse to load a file</li>
		 * 	<li>Once browse is used when saving a file</li>
		 * </ul>
		 *
		 * <p>The internal <code>InteractionStates</code> is used to know which case we are dealing with.
		 *
		 * @param event
		 *
		 */
		private function onFileSelectEventHandler(event:Event):void
		{
			fileReference.removeEventListener(Event.SELECT, onFileSelectEventHandler);
			fileReference.removeEventListener(Event.CANCEL, onFileCancelEventHandler);

			if (this.interactionState == InteractionStates.BROWSE)
				this.dispatchEvent( new LocalFileEvent( LocalFileEvent.FILE_LOAD_BROWSE ) );
			else
				this.dispatchEvent( new LocalFileEvent( LocalFileEvent.FILE_SAVE_BROWSE ) );
		}

		/**
		 * Method to be called in case the used decide to cancel the option to save or load a file.
		 * The method is related to the browse window.
		 *
		 * @param event
		 *
		 */
		private function onFileCancelEventHandler(event:Event):void
		{
			fileReference.removeEventListener(Event.SELECT, onFileSelectEventHandler);
			fileReference.removeEventListener(Event.CANCEL, onFileCancelEventHandler);

			this.dispatchEvent( new LocalFileEvent( LocalFileEvent.FILE_CANCEL ) );
		}

		/**
		 * In case an IOError gets called when trying to load or save a file this method will
		 * be dispatched.
		 *
		 * @param event
		 *
		 */
		private function onIOErrorEventHandler(event:Event):void
		{
			fileReference.removeEventListener(Event.COMPLETE, onCompleteEventHandler);
			fileReference.removeEventListener(IOErrorEvent.IO_ERROR, onIOErrorEventHandler);

			this.dispatchEvent( new LocalFileErrorEvent( "FileReference: IOErrorEvent.IO_ERROR received: "+event.toString() ) );
		}

		/**
		 * Method to handle the two cases where the complete gets calls:
		 *
		 * <ul>
		 * 	<li>Once complete loading a file</li>
		 * 	<li>Once coomplete saving a file</li>
		 * </ul>
		 *
		 * <p>The internal <code>InteractionStates</code> is used to know which case we are dealing with.
		 *
		 * @param event
		 *
		 */
		private function onCompleteEventHandler(event:Event):void
		{
			fileReference.removeEventListener(Event.COMPLETE, onCompleteEventHandler);
			fileReference.removeEventListener(IOErrorEvent.IO_ERROR, onIOErrorEventHandler);

			if (this.interactionState == InteractionStates.LOAD_FILE)
				this.dispatchEvent( new LocalFileLoadedEvent( fileReference.data ) );
			else
				this.dispatchEvent( new LocalFileEvent( LocalFileEvent.FILE_SAVE_SUCCESSFULLY ) );
		}
	}
}

Example:

  • To load a file: select the load button, which will prompt you to select a text file.
  • To save a file: type in the box and select the save button. You will be prompt to browse and select a place to save the file under.

5

16
Apr

Adobe AIR SQLite manager API for Adobe AIR 1.5 with password encryption

Back in January I posted a blog entry that show how to create a SQLite manger API that allow you to work with an application that has many SQL commands easily. These commands may be initialized from different classes and used in cases where we may want to keep the database connection open and avoid duplicating code. The orginal blog post can be found here.

SQLite Adobe AIR database

Adobe AIR 1.5 closed a security gap. The application is using the exact same database. Essentially any AIR application can read other applications database.

Adobe has updated the framework and now includes the ability to add additional properties to encrypt your SQLite database. Under the new security you can create an encrypted database and when attempting to open the database, your code must provide the database’s encryption key.

In the past few months I received many requests to add encryption to the API. I finally got a chance to update the code and have added the option to add a password to the database. The key is created using the Adobe’s EncryptionKeyGenerator class and is bond to Adobe’s license.

Here’s the method signature:


public function start(dbFullFileName:String, tableName:String, createTableStatement:String, password:String=null):void

In case you don’t need to include encryption to your database, just keep the password as “null”. Other than the start method and a method to generate a key I have updated. Additionally, I added custom events as well meta data for the event constants.


		public function start(dbFullFileName:String, tableName:String, createTableStatement:String, password:String=null):void
		{
			this.dbFullFileName = dbFullFileName;
			this.tableName = tableName;
			this.createDbStatement = createTableStatement;
			var encryptionKey:ByteArray = null;

			connection = new SQLConnection();
			sqlFile = File.applicationStorageDirectory.resolvePath(dbFullFileName);

			try
			{
				if (password != null)
				{
					encryptionKey = getEncryptionKey(password, sqlFile);
				}

				connection.open(sqlFile, SQLMode.CREATE, false, 1024, encryptionKey);

			    this.dispatchEvent(new DatabaseSuccessEvent(DatabaseSuccessEvent.DATABASE_CONNECTED_SUCCESSFULLY));
			}
			catch (error:SQLError)
			{
			    var errorMessage:String = "Error message:" + error.message;

			    if (error.details != "")
			    	errorMessage += ", Details:" + error.details;

			    fail(null, errorMessage);
			}
		}

The getEncryptionKey method takes the password and the File class validate the password that is 8-32 char with one letter lowercase letter as well as one upper case and one number. It return a ByteArray which being pass to the connection.open method.


		private function getEncryptionKey(password:String, sqlFile:File):ByteArray
		{
			var keyGen:EncryptionKeyGenerator = new EncryptionKeyGenerator();
			var encryptionKey:ByteArray;

			if (!keyGen.validateStrongPassword(password))
			{
				var errorMessage:String = "The password must be 8-32 char, " + "with one letter lowercase letter, " +
						"one upper case and one number";

				fail(null, errorMessage);

				return null;
			}

			encryptionKey = keyGen.getEncryptionKey(sqlFile, password);	

			return encryptionKey;
		}

To see an example of implementation, take a look at the code below:


<WindowedApplication xmlns="http://ns.adobe.com/mxml/2009" layout="absolute"
	initialize="initializeHandler(event)">

	<Script>
		<![CDATA[
			import com.elad.framework.sqlite.events.DatabaseSuccessEvent;
			import com.elad.framework.sqlite.events.StatementCompleteEvent;
			import com.elad.framework.sqlite.events.DatabaseFailEvent;
			import mx.collections.ArrayCollection;
			import mx.events.FlexEvent;
			import com.elad.framework.sqlite.SQLiteManager;

			private var database:SQLiteManager = SQLiteManager.getInstance();

			protected function initializeHandler(event:FlexEvent):void
			{
				database.addEventListener(DatabaseSuccessEvent.COMMAND_EXEC_SUCCESSFULLY, onSelectResult);
				database.addEventListener(DatabaseFailEvent.COMMAND_EXEC_FAILED, function(event:DatabaseFailEvent):void {
					trace("execution fail: "+event.errorMessage);
				});
				database.addEventListener(DatabaseFailEvent.DATABASE_FAIL, function(event:DatabaseFailEvent):void {
					trace("database fail: "+event.errorMessage);
				});
				database.addEventListener(DatabaseSuccessEvent.CREATING_DATABASE, function(event:DatabaseSuccessEvent):void {
					trace(event.message);
				});
				database.addEventListener(DatabaseSuccessEvent.DATABASE_CONNECTED_SUCCESSFULLY, onConnectedHandler);
				database.addEventListener(DatabaseSuccessEvent.DATABASE_READY, function():void { trace("database ready!"); } );

				// start database
				var password:String = "Pa55word";
				var createTableStatement:String = "CREATE TABLE Users(UserId VARCHAR(150) PRIMARY KEY, UserName VARCHAR(150))";
				database.start("Users.sql3", "Users", createTableStatement, password);
			}

			private function onConnectedHandler(event:DatabaseSuccessEvent):void
			{
				readEntries();
			}

			private function insertEntry():void
			{
				var sql:String =  "INSERT INTO Users VALUES('"+String(userId.text)+"','"+userName.text+"');";
				database.executeCustomCommand(sql);
			}

			private function readEntries():void
			{
				database.executeSelectAllCommand();
			}

			private function onSelectResult(event:StatementCompleteEvent):void
			{
				var result:Array = event.results.data;
				var rowsAffected:int = event.results.rowsAffected;  

				if (rowsAffected == 1)
					readEntries();

				if (result == null)
					return;

				var len:int = result.length;
				var dp:ArrayCollection = new ArrayCollection();

				for (var i:int; i<len; i++)
				{
					dp.addItem( {UserId: result[i].UserId, UserName: result[i].UserName} );
				}

				dataGrid.dataProvider = dp;
			}			

		]]>
	</Script>
	<Panel x="5" y="5" layout="absolute" height="356">

		<VBox horizontalScrollPolicy="off" verticalScrollPolicy="off">
			<!-- Form -->
			<Form width="414">
				<FormItem label="User ID:">
					<FxTextInput id="userId"/>
				</FormItem>
				<FormItem label="User Name:">
					<FxTextInput id="userName"/>
				</FormItem>
				<FormItem>
					<FxButton label="Insert Entry" click="insertEntry();"/>
				</FormItem>
			</Form>
		</VBox>

		<!-- Results -->
		<DataGrid x="19" y="123" id="dataGrid">
			<columns>
				<DataGridColumn headerText="User Id" dataField="UserId"/>
				<DataGridColumn headerText="User Name" dataField="UserName"/>
			</columns>
		</DataGrid>

	</Panel>

</WindowedApplication>

To download the complete code click here.

21
Feb

mp3tunes-as3-api open source API to use MP3Tunes in Flex/AIR/AS3 projects

mp3tunes is a service to load your audio files and share them between different devices such as: desktop, mobile, game consoles and the Web. I just completed creating a foundation API for AS3.0 so you can start using the API in your Flex/AIR/AS3.0 projects.
You can see the project in google code: http://code.google.com/p/mp3tunes-as3-api/
The SWC and ASDOC can be downloaded from here: http://mp3tunes-as3-api.googlecode.com/files/Archive.zip
And here’s a simple implementation of the API in Flex 4 (Gumbo). The code login into the MP3tunes Music API, retrieve album result, artist data, track data and plays the first audio file in your account.


<FxApplication xmlns="http://ns.adobe.com/mxml/2009"
	creationComplete="creationCompleteHandler(event)">
	<Script>
		<![CDATA[
			import flash.events.Event;
			import flash.media.Sound;
			import flash.net.URLRequest;
			import com.elad.MP3tunes.vo.TrackItemVO;
			import com.elad.MP3tunes.vo.TrackListVO;
			import com.elad.MP3tunes.events.TrackDataEvent;
			import com.elad.MP3tunes.vo.AlbumListVO;
			import com.elad.MP3tunes.events.AlbumDataEvent;
			import com.elad.MP3tunes.vo.ArtistItemVO;
			import com.elad.MP3tunes.vo.ArtistListVO;
			import mx.controls.Alert;
			import com.elad.MP3tunes.events.ArtistsResultEvent;
			import com.elad.MP3tunes.events.MusicEvent;
			import mx.events.FlexEvent;
			import com.elad.MP3tunes.Music;
			private var music:Music;
			protected function creationCompleteHandler(event:FlexEvent):void
			{
				music = new Music("YOUR_DEVELOPER_KEY");
				music.addEventListener(MusicEvent.LOGIN_SUCCESSFULL, onLogin);
				music.addEventListener(MusicEvent.LOGIN_ERROR, function():void { Alert.show("Error login"); } );
				music.login("YOUR_USERNAME", "YOUR_PASSWORD");
			}
			private function onLogin(event:MusicEvent):void
			{
				music.removeEventListener(MusicEvent.LOGIN_SUCCESSFULL, onLogin);
				music.addEventListener(ArtistsResultEvent.ARTIST_RESULT_COMPLETED, onArtistsResult);
				music.addEventListener(ArtistsResultEvent.ARTIST_RESULT_ERROR, function():void { Alert.show("Error getting artist list"); });
				music.getMusicByArtists();
			}
			private function onArtistsResult(event:ArtistsResultEvent):void
			{
				music.removeEventListener(ArtistsResultEvent.ARTIST_RESULT_COMPLETED, onArtistsResult);
				var artistList:ArtistListVO = new ArtistListVO(event.artistList.list.source);
				var item:ArtistItemVO = artistList.getItem(0);
				music.addEventListener(AlbumDataEvent.ALBUM_DATA_COMPLETED, onAlbumDataComplete);
				music.addEventListener(AlbumDataEvent.ALBUM_DATA_ERROR, function():void { Alert.show("Error getting album list"); });
				music.getAlbumData(item.artistId);
			}
			private function onAlbumDataComplete(event:AlbumDataEvent):void
			{
				music.removeEventListener(AlbumDataEvent.ALBUM_DATA_COMPLETED, onAlbumDataComplete);
				music.removeEventListener(AlbumDataEvent.ALBUM_DATA_ERROR, onAlbumDataComplete);
				var albumList:AlbumListVO = new AlbumListVO(event.collection.list.source);
				var albumId:String = albumList.getItem(0).albumId;
				music.addEventListener(TrackDataEvent.TRACK_DATA_COMPLETED, onTrackDataComplete);
				music.addEventListener(TrackDataEvent.TRACK_DATA_ERROR, function():void { Alert.show("Error getting track data"); });
				music.getTrackData(albumId);
			}
			private function onTrackDataComplete(event:TrackDataEvent):void
			{
				music.removeEventListener(AlbumDataEvent.ALBUM_DATA_COMPLETED, onAlbumDataComplete);
				music.removeEventListener(TrackDataEvent.TRACK_DATA_ERROR, function():void { Alert.show("Error getting track data"); });
				var trackList:TrackListVO = new TrackListVO(event.collection.list.source);
				var trackItem:TrackItemVO = trackList.getItem(0);
				playSong(trackItem.downloadURL);
			}
			private function playSong(url:String):void
			{
				var sound:Sound = new Sound();
				var req:URLRequest = new URLRequest(url);
				sound.load(req);
				sound.play();
			}
		]]>
	<Script>
<FxApplication>
</p>
09
Feb

Using Pixel Bender to do heavy lifting calculations, makes Flash Player multi-thread.

In Flash 10 Adobe added a compiler to handle filters, which is possible through Pixel Bender kernels. Pixel Bender kernels is a program that calculate a single pixel at run time.

This is how it works. The Pixel Bender graph is an XML language for combining individual pixel-processing operations called kernels, which are in PBK file format.
We can create the PBK XML file using Pixel Bender toolkit. After our kernel is ready we can then export it as a byte-code called PBJ. PBJ can then be used in the Flash 10 player.
Flex Gumbo Bender shader is the Pixel Bender kernels, which uses a separate thread than Flash Player to calculate a single pixel.

You can use the following math functions for in pixel bender for calculations:

1. sin(x) - Trigonometric sine function.
2. cos(x) - Cosine function.
3. tan(x) – Tangent function.
4. asin(x) - Arcsine (inverse sine) function.
5. acos(x) - inverse cosine (arccosine) function.
6. atan(x) - Arctangent (inverse tangent) function.
7. atan(x, y) - Arctangent (inverse tangent) function.
8. exp(x) - exponential function.
9. log(x) – Logarithm function.
10. pow(x, y) - power of function.
11. reciprocal(x) - multiplicative inverse function.
12. sqrt(x) - square root function.

The challenge with Flash related to single thread processing always caused issues, and while the Flash Player is processing information we cannot run another thread to do other things. We often find the player get “stuck” when doing heavy lifting. Pixel Bender can help in certain cases. I managed to create an example and an API that uses Pixel Bender to calculate information, while a video is playing. In the example here you can run calculations while the video is playing and compare performance with Flash Player doing the same calculation.

First play the video and hit the Pixel Bender calculation and while the video is playing calculations are being made in the background. The results are astonishing, using Flash Player to calculate 5M sine took about 10 seconds (on iMac 2.8GHz and 4GB memory) and the video paused. Using Pixel Bender the video had a light glitch and we received the results back for the 5M calculation after about 10 secounds, without the user noticing.


Pixel Bender for calculation

Create the pbj file in Pixel Bender Toolkit:


<languageVersion : 1.0;>
kernel SinCalculator
<
    namespace : "pixelBender";
    vendor : "Elad Elrom";
    version : 1;
    description : "Sin Calculator";
>
{
    input image1 src;
    output pixel3 result;

    void evaluatePixel()
    {
    	pixel1 value = pixel1(sin(sample(src, outCoord())));
    	result = pixel3(value, 0.0, 0.0);
    }
}

The API handle calculating with Pixel bender by splitting the process into chucks of 5,000 per kernal, since Pixel Bender produce an error message on large calculations.


package com.elad.framework.pixelBender
{
	import com.elad.framework.pixelBender.events.PixelBenderCalcEvent;

	import flash.display.Shader;
	import flash.display.ShaderJob;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.ByteArray;
	import flash.utils.Endian;

	[Event(name="completed", type="com.elad.pixelBender.events.PixelBenderCalcEvent")]

	/**
	 *  The PixelBenderCalculator class is the base class for math calculation with pixel bender
	 */
	public class PixelBenderCalculator extends EventDispatcher
	{
		public var kernalClass:Class;
		public var numberCollection:Array;

		private var shader:Shader;
		private var shaderJob:ShaderJob;
		private var input:ByteArray;
		private var output:ByteArray;
		private var retCollection:Array;
		private var requestsCounter:Number;
		private var numberOfRequest:Number;

		/**
		 * Number of calculations per kernal
		 */
		private const COLLECTION_SIZE:int = 5000;

		/**
		 * Default constructor for the pixel bender calculator
		 *
		 * @param numberCollection	collection contain numbers
		 * @param kernalClass	the kernal class of type pbj to be used to run the calculation
		 *
		 */
		public function PixelBenderCalculator(numberCollection:Array, kernalClass:Class)
		{
			reset();

			this.kernalClass = kernalClass;
			this.numberCollection = numberCollection;

			requestsCounter = numberCollection.length/COLLECTION_SIZE;
		}

		/**
		 * Method to start the calculation
		 *
		 */
		public function start():void
		{
		    output = new ByteArray();
		    output.endian = Endian.LITTLE_ENDIAN;

		    var start:int = numberOfRequest*COLLECTION_SIZE;
		    var end:int = ( (numberOfRequest+1)*COLLECTION_SIZE > numberCollection.length) ? numberCollection.length : ((numberOfRequest+1)*COLLECTION_SIZE);

		    input = convertArrayToByteArray(numberCollection, start, end);
		    createShaderJob();
		    numberOfRequest++;
		}

		/**
		 * Creates a shader class based on the kernal to pass the numbers and start the calculations.
		 *
		 * @see flash.display.ShaderJob
		 * @see flash.display.Shader
		 *
		 */
		private function createShaderJob():void
		{
			var width:int = input.length >> 2;
		    var height:int = 1;

		    shader = new Shader(new kernalClass());
		    shader.data.src.width = width;
		    shader.data.src.height = height;
		    shader.data.src.input = input;			    

		    shaderJob = new ShaderJob(shader, output, width, height);
		    shaderJob.addEventListener(Event.COMPLETE, shaderJobCompleteHandler);
		    shaderJob.start();
		}

		/**
		 * Static method to convert the array given into byte array.
		 *
		 * @param array
		 * @return
		 *
		 */
		private static function convertArrayToByteArray(array:Array, start:int, end:int):ByteArray
		{
			var retVal:ByteArray = new ByteArray();
		    var number:Number;

		    retVal.endian = Endian.LITTLE_ENDIAN;

		    for (var i:int=start; i<end; i++)
		    {
		    	number = Number(array[i]);
		    	retVal.writeFloat(number);
		    }

		    retVal.position = 0;
		    return retVal;
		}

		/**
		 * Convert the a <code>ByteArray</code> into an <code>Array</code>
		 *
		 * @param byteArray
		 * @return an array collection
		 *
		 */
		private function addByteArrayToCollection(byteArray:ByteArray):void
		{
		    var length:int = byteArray.length;
		    var number:Number;

		    for(var i:int=0; i<length; i+=4)
		    {
				number = byteArray.readFloat();
				if(i % 3 == 0)
				{
		    		retCollection.push(number);
		  		}
		    }
		}

		/**
		 * Handler for the shader once job is completed.
		 *
		 * @param event
		 *
		 */
		private function shaderJobCompleteHandler(event:Event):void
		{
		    output.position = 0;
			addByteArrayToCollection(output);

			input = null;
			output = null;

			if (requestsCounter>numberOfRequest)
			{
				start();
			}
			else
			{
				calculationCompleted();
			}

		}

		/**
		 * Method to dispatch an event once calculation is completed and reset this class.
		 *
		 */
		private function calculationCompleted():void
		{
			this.dispatchEvent( new PixelBenderCalcEvent(retCollection) );

			reset();
		}

		/**
		 * Method to clean up this class so we are not using un-needed memory.
		 *
		 */
		public function reset():void
		{
			retCollection = new Array();
			numberCollection = new Array();
			requestsCounter = 0;
			numberOfRequest = 0;
			numberCollection = null;
		}
	}
}

The implimentation is straight forward:


<FxApplication xmlns="http://ns.adobe.com/mxml/2009" xmlns:local="*" viewSourceURL="srcview/index.html">

	<!-- 

	////////////////////////////////////////////////////////////////////////////////
	//
	//  Elad Elrom (elad@elromdesign.com)
	//  Copyright 2009 Elorm LLC,
	//  All Rights Reserved.
	//
	//  NOTICE: Elad Elrom permits you to use, modify, and distribute this file
	//  in accordance with the terms of the license agreement accompanying it.
	//
	////////////////////////////////////////////////////////////////////////////////

 	@author  Elad Elrom

	-->

	<Script>
		<![CDATA[

			import com.elad.framework.pixelBender.PixelBenderCalculator;
			import com.elad.framework.pixelBender.events.PixelBenderCalcEvent;

			import mx.collections.ArrayCollection;
			import mx.collections.IList;
			import mx.events.FlexEvent;

			[Embed(source="SinCalculator.pbj", mimeType="application/octet-stream")]
			private var kernalClass:Class;
			private var pixelBenderCalc:PixelBenderCalculator;

			protected function startCalculatorPixelBender():void
			{
				// create a number collection
				var numberCollection:Array = new Array();

				for (var i:int=0; i<5000000; i++)
				{
					numberCollection.push( i );
				}

				// calculate
				pixelBenderCalc = new PixelBenderCalculator(numberCollection, kernalClass);
				pixelBenderCalc.addEventListener(PixelBenderCalcEvent.COMPLETED, onComplete );

				pixelBenderCalc.start();
			}

			private function startCalculatorFlashPlayer():void
			{
				// create a number collection
				var numberCollection:Array = new Array();

				for (var i:int=0; i<5000000; i++)
				{
					numberCollection.push( Math.sin(i) );
				}

				list.dataProvider = new ArrayCollection(numberCollection);
			}			

			private function onComplete(event:PixelBenderCalcEvent):void
			{
				list.dataProvider = new ArrayCollection(event.numberCollection);
				pixelBenderCalc.removeEventListener(PixelBenderCalcEvent.COMPLETED, onComplete);
			}

		]]>
	</Script>

	<List id="list" width="200" height="531.5"/>
	<Button label="Calculate with Pixel Bender" width="200" height="20" y="545" click="startCalculatorPixelBender()"/>
	<Button label="Calculate with Flash Player" width="200" height="20" y="574" click="startCalculatorFlashPlayer()"/>

	<VideoDisplay id="vid" width="355" height="290"
		source="http://thehq.tv/wp-content/uploads/flv/super-street-fighter-2-hd-round-1-trailer.flv"
		autoPlay="false" x="209" y="-1"/>
	<Button label="Play" click="vid.play();" x="213" y="303"/>

	<local:MemoryDashBoard  x="211" y="343"/>

</FxApplication>
23
Jan

Adobe AIR Mobile Touch and Multi-Touch screen applications

Today, there are already few systems that are using Adobe AIR to build a multi-touch GUI. There is an open project called Touchlib that allows listening to user gestures and building your own touch-screen. Intuilab has presented in Adobe MAX in Milan an application that takes full advantage of surface computers. The system integrates many contents such as: image, video, web content, Adobe pdf, Flash, Illustrator, Photoshop, Microsoft Office etc.

The idea behind Touch and Multi-touch computer screens is to follow the instructions of finger/s or hand/s and be able to track gestures. The idea is simple:

1. Register to receive gestures events.
2. Handle gesture events.
3. Interpret the gesture events.

Creating a touch screen application on UMPC can be done today, and we can rely on the UMPC device to handle the touch screen; however you may find that relying entirely on the device may be buggy.

For instance, the device may register a user gesture when your fingertip is on the device and didn’t even move your finger since a slight move also get registered, however you may not need your application to be that sensitive.

I recommend implementing your own instructions to register user gestures. Microsoft released the beta version of Windows 7 which track user gestures via WM_GESTURECOMMAND, WM_GESTURE and WM_TOUCH so I think it’s possible to communicate with these method using a proxy such as Merapi or you can create your Java proxy.

I have created a simple POC that I havn’t test much but it gives you an idea. The API is for touch screen application for mobile devices such as UMPC that register user gestures and can be expended to register multi-touch screen user gestures. The way it works is that the API translate the mouse events into user gestures based on time and movement.

Here’s a screen shot of the application on a UMPC:
AIR Touch Screen

And you can download it from here:
http://elromdesign.com/blog/Flex/TouchScreen/TouchScreen/Touchscreen.zip

Take a look at the class below that gives its own instructions on when to register user gestures.


package com.elad.framework.touchscreen
{
	import com.elad.framework.touchscreen.events.TouchEvent;
	import com.elad.framework.touchscreen.vo.TouchVO;

	import flash.events.EventDispatcher;
	import flash.events.MouseEvent;
	import flash.utils.Timer;

	import mx.core.UIComponent;

	public class TouchManager extends EventDispatcher
	{
		private var moveTimer:Timer;
		private var previousX:int = 0;
		private var previousY:int = 0;
		private var component:UIComponent;

		public function TouchManager(component:UIComponent)
		{
			this.component = component;
		}

		public function start():void
		{
			initialize();
			this
		}

		public function stop():void
		{
			component.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDownHandler);
			component.removeEventListener(MouseEvent.MOUSE_UP, onMouseUpHandler);

			moveTimer.stop();
			moveTimer = null;
		}		

		protected function initialize():void
		{
			component.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDownHandler);
			component.addEventListener(MouseEvent.MOUSE_UP, onMouseUpHandler);
		}

		private function startTimer():void
		{
			moveTimer = new Timer(100,1000);
			moveTimer.start();
		}

		private function onMouseDownHandler(event:MouseEvent):void
		{
			startTimer();
			component.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveHandler);

			var touch:TouchVO = new TouchVO(this.previousX, this.previousY, event.localX, event.localY, this.moveTimer.currentCount);
			this.dispatchEvent( new TouchEvent(	TouchEvent.TOUCH_DOWN, touch ) );
		}

		private function onMouseUpHandler(event:MouseEvent):void
		{
			moveTimer.stop();
			component.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveHandler);

			var touch:TouchVO = new TouchVO(this.previousX, this.previousY, event.localX, event.localY, this.moveTimer.currentCount);
			this.dispatchEvent( new TouchEvent(	TouchEvent.TOUCH_UP, touch ) );
		}

		private function onMouseMoveHandler(event:MouseEvent):void
		{
			var isMove:Boolean = isTouchMove(event.localX, event.localY);
			var touch:TouchVO = new TouchVO(this.previousX, this.previousY, event.localX, event.localY, this.moveTimer.currentCount);

			if (isMove)
			{
				this.dispatchEvent( new TouchEvent(TouchEvent.TOUCH_DRAG, touch) );
			}
		}

		private function isTouchMove(x:int, y:int):Boolean
		{
			var retVal:Boolean = false;
			var ignore:int = 3;
			var isXmoved:Boolean;
			var isYmoved:Boolean;

			if (previousX != 0 && previousY != 0)
			{
				isXmoved = (x > previousX+ignore || x < previousX-ignore) ? true : false;
				isYmoved = (y > previousY+ignore || y < previousY-ignore) ? true : false;

				if ( isXmoved || isYmoved )
				{
					retVal=true;
				}
			}

			previousX = x;
			previousY = y;

			return retVal;
		}		

	}
}

And than we can implement the class with a simple interface:


<WindowedApplication xmlns="http://ns.adobe.com/mxml/2009" layout="absolute"
	width="800" height="600"
	initialize="initializeHandler()">

	<Script>
		<![CDATA[
			import com.elad.framework.touchscreen.vo.TouchVO;
			import com.elad.framework.touchscreen.events.TouchEvent;
			import com.elad.framework.touchscreen.TouchManager;
			import mx.collections.ArrayCollection;

			[Bindable]
			private var arrayCollection:ArrayCollection = new ArrayCollection;

			private var touch:TouchManager;

			protected function initializeHandler():void
			{
				touch = new TouchManager(this);
				touch.addEventListener(TouchEvent.TOUCH_DOWN, onMouseDownHandler);
				touch.addEventListener(TouchEvent.TOUCH_UP, onMouseUpHandler);
				touch.addEventListener(TouchEvent.TOUCH_DRAG, onMouseDragHandler);

				touch.start();
			}						

			private function onMouseDownHandler(event:TouchEvent):void
			{
				ellipse.visible = true;
				moveEllipse(event.touchVO.currentX, event.touchVO.currentY);
				registerLocation(ellipse.x, ellipse.y, "MouseDown", 0);
			}	

			private function onMouseUpHandler(event:TouchEvent):void
			{
				ellipse.visible = false;
			}					

			private function onMouseDragHandler(event:TouchEvent):void
			{
					moveEllipse(event.touchVO.currentX, event.touchVO.currentY);
					registerLocation(ellipse.x, ellipse.y, "MouseMove", event.touchVO.moveTimer);
			}

			private function registerLocation(x:int, y:int, type:String, time:int):void
			{
				arrayCollection.addItem({locationX: ellipse.x, locationY: ellipse.y, type: type, time: time});
				dg.dataProvider = arrayCollection;
			}

			private function moveEllipse(x:int, y:int):void
			{
				ellipse.x = x - ellipse.width/2;
				ellipse.y = y - ellipse.height/2;
			}			

		]]>
	</Script>

	<Group>
		<Ellipse height="80" width="80" id="ellipse" visible="false">
			<stroke>
				<SolidColorStroke color="0x000000" weight="2"/>
			</stroke>
		</Ellipse>
	</Group>

	<DataGrid x="341" y="4" id="dg" dataProvider="{arrayCollection}" height="410">
		<columns>
			<DataGridColumn headerText="X" dataField="locationX"/>
			<DataGridColumn headerText="Y" dataField="locationY"/>
			<DataGridColumn headerText="Type" dataField="type"/>
			<DataGridColumn headerText="Time" dataField="time"/>
		</columns>
	</DataGrid>

I am looking for a .NET developer to spare time to create a simple class that gets Windows 7 events and compile them into a DLL, so we can pick them up in a Java proxy, which will make a request every few millisecond and look for user gesture changes, so if you are interested, please reply in this blog post.

13
Jan

Flex 4 Gumbo FXG - tips for creating custom FxVScrollBar and FxHScrollBar components

Flex Gumbo FXG offers new APIs to work with vertical and horizontal scrollers. There are two components: FxVScrollBar and FxHScrollBar, which extends the FxScrollBar class. You can use Flash Catalyst to generate a custom scrollers easily or create the skin on your own. Once the code is completed it’s not clear what you need to do with the code or how to attach it to FXG components, since there are no examples in the live docs available yet, I decided to put a post on my blog.

Four properties are available to set the scroller to a FXG component;

1. Minimum - Min value for the scroller.
2. Maximum - Max value for the scroller.
3. Value - Current position of the scroller, which must be within the min and max values and needs to be attached to some component.
4. Viewport size - number of items in the range that can displayed at a time.

Use the FxVScrollBar.value property to attach to a group component as shown below;


	<Group id="group" width="320" height="1500">
		..
		..
	</Group>

	<FxVScrollBar id="pageScrollbar"
		includeIn="SomeState"
skinClass="com.elad.mobilevideo.view.components.PageScrollbar"
		height="100%"
		value="@{group.verticalScrollPosition}"
		maximum="{group.height}"
		/>

Notice that I used the “includeIn” property to include the scroller component in “SomeState” state. Also note that the skin points to a custom component PageScrollBar.mxml, which contains the skin that holds both the scroll bar and scroll thumb components, take a look;


<Skin xmlns="http://ns.adobe.com/mxml/2009" xmlns:d="http://ns.adobe.com/fxg/2008/dt"
	 height="100%" xmlns:th="http://ns.adobe.com/thermo/2009">

	<transitions>
		<Transition fromState="normal" toState="disabled"/>
		<Transition fromState="disabled" toState="normal"/>
	</transitions>

	<states>
		<State name="normal" th:color="0xcc0000"/>
		<State name="disabled" th:color="0x0081cc"/>
	</states>

	<Metadata>[HostComponent("mx.components.FxVScrollBar")]</Metadata>
	<FxButton left="0" top="0" skinClass="com.elad.mobilevideo.view.components.ScrollTrack" id="track"/>
	<FxButton left="1" top="1" skinClass="com.elad.mobilevideo.view.components.ScrollThumb" id="thumb"/>
</Skin>

ScrollThumb.mxml component:


<Skin xmlns="http://ns.adobe.com/mxml/2009" xmlns:d="http://ns.adobe.com/fxg/2008/dt" resizeMode="scale">

	<transitions>
		<Transition fromState="up" toState="over"/>
		<Transition fromState="up" toState="down"/>
		<Transition fromState="up" toState="disabled"/>
		<Transition fromState="over" toState="up"/>
		<Transition fromState="over" toState="down"/>
		<Transition fromState="over" toState="disabled"/>
		<Transition fromState="down" toState="up"/>
		<Transition fromState="down" toState="over"/>
		<Transition fromState="down" toState="disabled"/>
		<Transition fromState="disabled" toState="up"/>
		<Transition fromState="disabled" toState="over"/>
		<Transition fromState="disabled" toState="down"/>
	</transitions>

	<states>
		<State name="up"/>
		<State name="over"/>
		<State name="down"/>
		<State name="disabled"/>
	</states>

	<Metadata>[HostComponent("mx.components.FxButton")]</Metadata>
	<BitmapGraphic source="@Embed('assets/SearchVideo/thumb.png')"
		d:userLabel="thumb"
		left="0" top="0"/>

</Skin>

ScrollTrack.mxml component:


<Skin xmlns="http://ns.adobe.com/mxml/2009" xmlns:d="http://ns.adobe.com/fxg/2008/dt" resizeMode="scale">

	<transitions>
		<Transition fromState="up" toState="over"/>
		<Transition fromState="up" toState="down"/>
		<Transition fromState="up" toState="disabled"/>
		<Transition fromState="over" toState="up"/>
		<Transition fromState="over" toState="down"/>
		<Transition fromState="over" toState="disabled"/>
		<Transition fromState="down" toState="up"/>
		<Transition fromState="down" toState="over"/>
		<Transition fromState="down" toState="disabled"/>
		<Transition fromState="disabled" toState="up"/>
		<Transition fromState="disabled" toState="over"/>
		<Transition fromState="disabled" toState="down"/>
	</transitions>

	<states>
		<State name="up"/>
		<State name="over"/>
		<State name="down"/>
		<State name="disabled"/>
	</states>

	<Metadata>[HostComponent("mx.components.FxButton")]</Metadata>
	<BitmapGraphic source="@Embed('assets/SearchVideo/slider.png')"
		resizeMode="scale" d:userLabel="slider"
		left="0" top="0"/>

</Skin>

I didn’t implement the “Transition” components but feel free to play around with these components to create interesting custom scrollers.

12
Jan

Flex Gumbo FXG use DataGroup instead of Repeater

Flex Gumbo SDK Iteration 10 FXG has an issue with the Repeater object the content property is not binding, I submited a ticket to Adobe here.

The code that repoduce the error is below:


   <mx:Repeater id="r" dataProvider="{ar}">
<FxButton left="20" top="335" content="{r.currentItem}" skinClass="components.Button" />
    </mx:Repeater> 

I couldn’t find any workaround and end up using the ‘DataGroup’ FXG object instead. The DataGroup allow you to do what the repeater does so I am not going to be surprised if Adobe decide to deprecate the component.


    <DataGroup>
        <layout><VerticalLayout gap="1" /></layout>
        <dataProvider>
            <ArrayCollection>
				<FxButton id="btn4" left="20" top="335" content="{arrayCollection.getItemAt(0)}"
					skinClass="components.Button" includeIn="SearchPage" />
            </ArrayCollection>
        </dataProvider>
    </DataGroup>

Or you can set the array collection as any component and attach it;


<DataGroup dataProvider="{arrayCollection}" />
27
Mar

Increase memory performance on data structures using HashMapCollection API

Hash map is a data structure that can associates keys with values. It provides constant time performance for the basic operations of addItem, removeItemAt, getItemValue and other methods. The main reason for the increase in memory performance is iteration. Unlike ArrayCollection, the HashMapCollection doesn’t need to loop through in order to find a key or a value. The HashMapCollection associates a value with a key.HashMap Collection is a great choice when you need to:

  1. When you need in-memory data structures.
  2. When you need a pair of key-value data structure.
  3. When you need to Increase performance.

Flex and Action Script provides the “IList” interface that is implemented by the Array and ArrayCollection. Using array is a great choice when you need to associate an object with an ID, but if you have scenarios where you want to copy a large database structure to the memory or have a key-values pairs you will have to iteration through the array in order to do a lookup.
The API I developed is called “HashMapCollection” and it is using naming conventions that are very similar to the “ListCollectionView”.

The API is also implementing the change events classes, so you will be able to start using the API right away with much similarity to the “IList” methods.

Let’s take an example. Say we have list of contacts and phone numbers and we need to store them in the memory, the list was given to us through the SQL database and it has about 10,000 contacts.

Action Scriot HashMap Collection example

Using Array or ArrayCollection will force us to loop through the list until we find the name we are looking for and then get the phone number, that process will put a large expense on Flash player and will basically look as if the program is stuck, since action script is a single thread programming language, which means that the program cannot split itself into more than one simultaneously tasks.

The HashMapCollection doesn’t need to loop it’s basically using the following code to find the value:

map[key] = value;

I am giving a list of contacts, as an example, but let me make it clear that the HashMapCollection is flexible to use any type of key-value such as;

• Name-UIComponents
• Name-Object
• Name-Array

Below you can find a use-case example of creating a data structure and modifying the data;


private function map():void {

var map:IMap = new HashMapCollection();
map.addEventListener(CollectionEvent.COLLECTION_CHANGE, handler);

map.addItem(”John”, “212-452-8086″);
map.addItem(”James”, “718-345-3455″);
map.addItem(”Micheal”, “917-782-8822″);
map.addItem(”Ron”, “212-426-8855″);
map.addItem(”Mike”, “212-255-2436″);
map.addItem(”Jenny”, “718-344-2433″);
map.addItem(”Jack”, “917-222-4352″);
map.addItem(”Riki”, “981-222-1122″);
trace(”\nAll items: “+map.toString()+”\n”);

trace(”containsKey Jack? “+map.containsKey(”Jack”));
trace(”containsValue 718-344-2433? “+map.containsValue(”718-344-2433″));
trace(”getItemKey 718-344-2433: “+map.getItemKey(”718-344-2433″));
trace(”getItemValue Jenny: “+map.getItemValue(”Jenny”));

map.removeItemAt(”Riki”);
trace(”Remove Riki.”);
trace(”getItemValue Riki: “+map.getItemValue(”Riki”));
trace(”Comapre: “+map.compare(”Ron”, “212-426-8855″));

map.removeAll();
trace(”\nAll items: “+map.toString()+”\n”);

}

private function handler(event:CollectionEvent):void
{
trace(”Event: “+event.kind);
}

All of this is great, but let me actually prove to you that you gain performance. I created a small Flex application that creates two data structures; ArrayCollection and HashMapCollection. Watch the memory monitor and see how Flash Player actaully stopped while it loops and looks for the key in the ArrayCollection. On the other hand the HashMap doesn’t add any expense on the Flash player.

All source code contained in this API has been published under the MIT licence and is protected as stated therein.

To see the example and download source code (via right click):
http://elromdesign.com/blog/Flex/API/HashMapCollection/

To view the ASDOC of all the API:
http://elromdesign.com/blog/Flex/API/asdoc/

Have fun!