Back in the summer, I was lucky enough to get my hands on some early builds of Stage3D for mobile. I built some simple examples, including basic geometric shapes and simple 3D bubble charts inside of mobile Flex/AIR applications. I have been asked numerous times for the source code, and I’ve finally given in, and am sharing some source code.
I am not posting the full mobile application source code, since Stage3D for mobile is not yet available. However, I have ported the 3D bubble chart example to run in a Flex application targeting the desktop (Flash Player 11). The bubble chart example extends the concepts explored in the basic geometric shapes example.
Before you say “shoot, he didn’t give us the mobile code”, let me explain… When I ported the code from the mobile project to the desktop Flex project, all I changed was code specific to the mobile Flex framework. I changed <s:ViewNavigatorapplication> to <s:Application> and the corresponding architecture changes that were required, and I changed the list item renderers to Spark item renderers based on <s:Group> instead of mobile item renderers. In the mobile item renderers, all my drawing logic was done using the ActionScript drawing API. For simplicity in the port, I just used <s:Rect> to add the colored regions in the desktop variant.
That is all I changed!
The stage3D code between the desktop and mobile implementations is identical. You can see the desktop port in action in the video below:
The source code was intended to be exploratory at best… I was simply experimenting with hardware accelerated content, and how it can be used within your applications. There is one big “gotcha” that you will have to watch out for if you want Stage3D content within a Flex application… Stage3D content shows up behind Flex content on the display list. By default, Flex apps have a background color, and they will hide the Stage3D content. If you want to display any Stage3D content within a Flex application (regardless of web, desktop AIR, or mobile), you must set the background alpha of the Flex application to zero (0). Otherwise you will pull out some hair trying to figure out why it doesn’t show up.
The source code for the web/Flex port of this example is available at:
After spending some time playing around sketching with the HTML5 canvas element earlier this week, I figured “why not add some ‘enterprise’ concepts to this example?”… Next thing you know we’ve got a multi-device shared sketching/collaboration experience.
To keep things straightforward, I chose to demonstrate the near-realtime collaboration using a short-interval HTTP poll. HTTP polling is probably the simplest form of near-realtime data in web applications, however you may experience lag when compared to a socket connection of equivalent functionality. I’ll discuss the various realtime data options you have in Flex/Flash and HTML/JS and their pros & cons further in this post.
What you’ll see in the video below is the sketching example with realtime collaboration added using short-interval data polling of a ColdFusion application server. The realtime collaboration is shown between an iPad 2, a Kindle Fire, and a Macbook Pro.
Before we get into the code for this example, let’s first review some realtime data basics…
First, why/when would you need realtime data in your applications? Here are just a few:
Time sensitive information, where any delay could have major repercussions
Realtime financial information
Emergency services (medical, fire, police)
Military/Intelligence scenarios
Business critical efficiency/performance metrics
Collaboration
Realtime audio/video collaboration
Shared experience (presentations/screen sharing)
Entertainment
Streaming media (audio/video)
Gaming
Regardless of whether you are building applications for mobile, the web, or desktop, using any technology (Flex/Flash, HTML/JS, Java, .NET, Objective C, or C/C++ (among others)), there are basically 3 methods for streaming/realtime data:
Socket Connection
HTTP Polling
HTTP Push
Socket Connections
Socket connectionss are basically end-to-end communications channels between two computer processes. Your computer (a client) connects to a server socket and establishes a persistent connection that is used to pass data between the client and server in near-realtime. Persistent socket connections are generally based upon TCP or UDP and enable asynchronus bidirectional communication. Binary or Text-based messages can be sent in either direction at any point in time, in any sequence, as data is available. In HTML/JS applications you can use web sockets, which I recently discussed, or use a plugin that handles realtime socket communication. Did you also know that the next version of ColdFusion will even have web socket support built in? In Flash/Flex/AIR, this can be achieved using the RTMP protocol (LCDS, Flash Media Server, etc…) or raw sockets (TCP or UDP).
Direct Socket Communications
In general, direct socket based communication is the most efficient means of data transfer for realtime application scenarios. There is less back and forth handshaking and less packet encapsulation required by various protocols (HTTP, etc…), and you are restricted by fewer network protocol rules. However, socket based communications often run on non-standard or restricted ports, so they are more likely to be blocked by IT departments or stopped by network firewalls. If you are using socket based communication within your applications, which are running on non-standard ports, and you don’t govern the network, you may want a fallback to another realtime data implementation for failover cases.
HTTP Polling
HTTP Polling is the process of using standard HTTP requests to periodically check for data updates on the server. The client application requests information from the server. Generally, the client will send a timestamp indicating the last data update time. If there is information available on the server that is newer than the timestamp, that data will be immediately sent back to the client (and the client’s timestamp will be updated). After a period of time, another request will be made, and so forth until the polling is stopped within the application. Using this approach, the application is more-or-less “phoning home” periodically to the server to see if there are any updates. You can achieve near-realtime performance by setting a very short polling interval (less than one second).
Basic Data Poll Sequence
HTTP polling uses standard web protocols and ports, and generally will not be blocked by firewalls. You can poll on top of standard HTTP (port 80) or HTTPS (port 443) without any issue. This can be achieved by polling JSON services, XML Services, AMF, or any other data format on top of a HTTP request. HTTP polling will generally be slower than a direct socket method, and will also utilize more network bandwidth b/c of request/response encapsulation and the periodic requests to the server. It is also important to keep in mind that the HTTP spec only allows for 2 concurrent connections to a server at any point in time. Polling requests can consume HTTP connections, thus slowing load time for other portions of your application. HTTP polling can be employed in HTML/JS, Flex/Flash/AIR, desktop, server, or basically any other type of application using common libraries & APIs.
HTTP Push
HTTP Push technologies fall into 2 general categories depending upon the server-side technology/implementation. This can refer to HTTP Streaming, where a connection is opened between the client and server and kept open using keep-alives. As data is ready to send to the client, it will be pushed across the existing open HTTP connection. HTTP Push can also refer to HTTP Long Polling, where the client will periodically make a HTTP request to the server, and the server will “hold” the connection open until data is available to send to the client (or a timeout occurs). Once that request has a complete response, another request is made to open another connection to wait for more data. Once Again, with HTTP Long Poll there should be a very short polling interval to maintain near-realtime performance, however you can expect some lag.
HTTP Long Poll Sequence
HTTP Streaming & HTTP Long polling can be employed in HTML/JS applications using the Comet approach (supported by numerous backend server technologies) and can be employed in Flex/Flash/AIR using BlazeDS or LCDS.
Collaborative Applications
Now back to the collaborative sketching application shown in the video above… the application builds off of the sketching example from previous blog posts. I added logic to monitor the input sketches and built a HTTP poll-based monitoring service to share content between sessions that share a common ID.
Realtime Collaborative Sketches
In the JavaScript code, I created an ApplicationController class that acts as an observer to the input from the Sketcher class. The ApplicationController encapsulates all logic handling data polling and information sharing between sessions. When the application loads, it sets up the polling sequence.
The polling sequence is setup so that a new request will be made to the server 250MS after receiving a response from the previous request. Note: this is very different from using a 250MS interval using setInterval. This approach guarantees 250MS from response to the next request. If you use a 250MS interval using setInterval, then you are only waiting 250MS between each request, without waiting for a response. If your request takes more than 250 MS, you will can end up have stacked, or “concurrent” requests, which can cause serious performance issues.
When observing the sketch input, the start and end positions and color for each line segment get pushed into a queue of captured transactions that will be pushed to the server. (The code supports multiple colors, even though there is no method to support changing colors in the UI.)
When a poll happens, the captured transactions are sent to the server (a ColdFusion CFC exposed in JSON format) as a HTTP post.
ApplicationController.prototype.poll = function () {
this.pendingTransactions = this.capturedTransactions;
this.capturedTransactions = [];
var data = { "method":"synchronize",
"id":this.id,
"timestamp":this.lastTimeStamp,
"transactions": JSON.stringify(this.pendingTransactions),
"returnformat":"json" };
var url = "services/DataPollGateway.cfc";
$.ajax({
type: 'POST',
url: url,
data:data,
success: this.getRequestSuccessFunction(),
error: this.getRequestErrorFunction()
});
}
The server then stores the pending transactions in memory (I am not persisting these, they are in-ram on the server only). The server checks the transactions that are already in memory against the last timestamp from the client, and it will return all transactions that have taken place since that timestamp.
<cffunction name="synchronize" access="public" returntype="struct">
<cfargument name="id" type="string" required="yes">
<cfargument name="timestamp" type="string" required="yes">
<cfargument name="transactions" type="string" required="yes">
<cfscript>
var newTransactions = deserializeJSON(transactions);
if( ! structkeyexists(this, "id#id#") ){
this[ "id#id#" ] = ArrayNew(1);
}
var existingTransactions = this[ "id#id#" ];
var serializeTransactions = ArrayNew(1);
var numberTimestamp = LSParseNumber( timestamp );
//check existing tranactions to return to client
for (i = 1; i lte ArrayLen(existingTransactions); i++) {
var item = existingTransactions[i];
if ( item.timestamp GT numberTimestamp ) {
ArrayAppend( serializeTransactions, item.content );
}
}
var newTimestamp = GetTickCount();
//add new transactions to server
for (i = 1; i lte ArrayLen(newTransactions); i++) {
var item = {};
if ( structkeyexists( newTransactions[i], "clear" )) {
serializeTransactions = ArrayNew(1);
existingTransactions = ArrayNew(1);
}
item.timestamp = newTimestamp;
item.content = newTransactions[i];
ArrayAppend( existingTransactions, item );
}
var result = {};
result.transactions = serializeTransactions;
result.timestamp = newTimestamp;
this[ "id#id#" ] = existingTransactions;;
</cfscript>
<cfreturn result>
</cffunction>
When a poll request completes, any new transactions are processed and a new poll is requested.
ApplicationController.prototype.getRequestSuccessFunction = function() {
<pre> var self = this;
return function( data, textStatus, jqXHR ) {
var result = eval( "["+data+"]" );
if ( result.length > 0 )
{
var transactions = result[0].TRANSACTIONS;
self.lastTimeStamp = parseInt( result[0].TIMESTAMP );
self.processTransactions( transactions );
}
self.pendingTransactions = [];
self.requestPoll();
}
}
You can access the full client and server application source on Github at:
One growing trend that I have seen in mobile & tablet applications is the creation of tools that enable your workforce to perform their job better. This can be in the case of mobile data retrieval, streamlined sales process with apps for door-to-door sales, mobile business process efficiency, etc…
One of the topics that comes up is how do you capture a signature and store it within your application? This might be for validation that the signer is who they say they are, or for legal/contractual reasons. Imagine a few scenarios:
Your cable TV can’t be installed until you sign the digital form on the installation tech’s tablet device
You agree to purchase a service from a sales person (door to door, or in-store kiosk) – your signature is required to make this legally binding.
Your signature is required to accept an agreement before confidential data is presented to you.
These are just a few random scenarios, I’m sure there are many more. In this post, I will focus on 2 (yes, I said two) cross-platform solutions to handle this task – one built with Adobe Flex & AIR, and one built with HTML5 Canvas & PhoneGap.
Watch the video below to see this in action, then we’ll dig into the code that makes it work.
The basic flow of the application is that you enter an email address, sign the interface, then click the green “check” button to submit to the signature to a ColdFusion server. The server then sends a multi-part email to the email address that you provided, containing text elements as well as the signature that was just captured.
If you’d like to jump straight to specific code portions, use the links below:
Let’s first examine the server component of the sample application. The server side is powered by ColdFusion. There’s just a single CFC that is utilized by both the Flex/AIR and HTML/PhoneGap front-end applications. The CFC exposes a single service that accepts two parameters: the email address, and a base-64 encoded string of the captured image data.
<cffunction name="submitSignature" access="remote" returntype="boolean">
<cfargument name="email" type="string" required="yes">
<cfargument name="signature" type="string" required="yes">
<cfmail SUBJECT ="Signature"
FROM="#noReplyAddress#"
TO="#email#"
username="#emailLoginUsername#"
password="#emailLoginPassword#"
server="#mailServer#"
type="HTML" >
<p>This completes the form transaction for <strong>#email#</strong>.</p>
<p>You may view your signature below:</p>
<p><img src="cid:signature" /></p>
<p>Thank you for your participation.</p>
<cfmailparam
file="signature"
content="#toBinary( signature )#"
contentid="signature"
disposition="inline" />
</cfmail>
<cfreturn true />
</cffunction>
Note: I used base-64 encoded image data so that it can be a single server component for both user interfaces. In Flex/AIR you can also serialize the data as a binary byte array, however binary serialization isn’t quite as easy with HTML/JS… read on to learn more.
The Flex/AIR Solution
The main user interface for the Flex/AIR solution is a simple UI with some form elements. In that UI there is an instance of my SignatureCapture user interface component. This is a basic component that is built on top of UIComponent (the base class for all Flex visual components), which encapsulates all logic for capturing the user signature. The component captures input based on mouse events (single touch events are handled as mouse events in air). The mouse input is then used to manipulate the graphics content of the component using the drawing API. I like to think of the drawing API as a language around the childhood game “connect the dots”. In this case, you are just drawing lines from one point to another.
When the form is submitted, the graphical content is converted to a base-64 encoded string using the Flex ImageSnapshot class/API, before passing it to the server.
You can check out a browser-based Flex version of this in action at http://tricedesigns.com/portfolio/sigCaptureFlex/ – Just enter a valid email address and use your mouse to sign within the signature area. When this is submitted, it will send an email to you containing the signature.
package
{
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Point;
import mx.core.UIComponent;
import mx.graphics.ImageSnapshot;
import mx.managers.IFocusManagerComponent;
import spark.primitives.Graphic;
public class SignatureCapture extends UIComponent
{
private var captureMask : Sprite;
private var drawSurface : UIComponent;
private var lastMousePosition : Point;
private var backgroundColor : int = 0xEEEEEE;
private var borderColor : int = 0x888888;
private var borderSize : int = 2;
private var cornerRadius :int = 25;
private var strokeColor : int = 0;
private var strokeSize : int = 2;
public function SignatureCapture()
{
lastMousePosition = new Point();
super();
}
override protected function createChildren():void
{
super.createChildren();
captureMask = new Sprite();
drawSurface = new UIComponent();
this.mask = captureMask;
addChild( drawSurface );
addChild( captureMask );
this.addEventListener( MouseEvent.MOUSE_DOWN, onMouseDown );
}
protected function onMouseDown( event : MouseEvent ) : void
{
lastMousePosition = globalToLocal( new Point( stage.mouseX, stage.mouseY ) );
stage.addEventListener( MouseEvent.MOUSE_MOVE, onMouseMove );
stage.addEventListener( MouseEvent.MOUSE_UP, onMouseUp );
}
protected function onMouseMove( event : MouseEvent ) : void
{
updateSegment();
}
protected function onMouseUp( event : MouseEvent ) : void
{
updateSegment();
stage.removeEventListener( MouseEvent.MOUSE_MOVE, onMouseMove );
stage.removeEventListener( MouseEvent.MOUSE_UP, onMouseUp );
}
protected function updateSegment() : void
{
var nextMousePosition : Point = globalToLocal( new Point( stage.mouseX, stage.mouseY ) );
renderSegment( lastMousePosition, nextMousePosition );
lastMousePosition = nextMousePosition;
}
public function clear() : void
{
drawSurface.graphics.clear();
}
override public function toString() : String
{
var snapshot : ImageSnapshot = ImageSnapshot.captureImage( drawSurface );
return ImageSnapshot.encodeImageAsBase64( snapshot );
}
override protected function updateDisplayList(w:Number, h:Number):void
{
super.updateDisplayList(w,h);
drawSurface.width = w;
drawSurface.height = h;
var g : Graphics = this.graphics;
//draw rectangle for mouse hit area
g.clear();
g.lineStyle( borderSize, borderColor, 1, true );
g.beginFill( backgroundColor, 1 );
g.drawRoundRect( 0,0,w,h, cornerRadius, cornerRadius );
//fill mask
g.clear();
g = captureMask.graphics;
g.beginFill( 0, 1 );
g.drawRoundRect( 0,0,w,h, cornerRadius, cornerRadius );
}
protected function renderSegment( from : Point, to : Point ) : void
{
var g : Graphics = drawSurface.graphics;
g.lineStyle( strokeSize, strokeColor, 1 );
g.moveTo( from.x, from.y );
g.lineTo( to.x, to.y );
}
}
}
The HTML5/PhoneGap Solution
The main user interface for the HTML5/PhoneGap solution is also a simple UI with some form elements. In that UI there is a Canvas element that is used to render the signature. I created a SignatureCapture JavaScript class that encapsulates all logic for capturing the user signature. In browsers that support touch events (mobile browsers), this is based on the touchstart, touchmove and touchend events. In browsers that don’t support touch (aka desktop browsers), the signature input is based on mousedown, mousemove and mouseup events. The component captures input based on touch or mouse events, and that input is used to manipulate the graphics content of the Canvas tag instance. The canvas tag also supports a drawing API that is similar to the ActionScript drawing API. To read up on Canvas programmatic drawing basics, check out the tutorials at http://www.adobe.com/devnet/html5/html5-canvas.html
When the form is submitted, the graphical content is converted to a base-64 encoded string using the Canvas’s toDataURL() method. The toDataURL() method returns a base-64 encoded string value of the image content, prefixed with “data:image/png,”. Since I’ll be passing this back to the server, I don’t need this prefix, so it is stripped, then sent to the server for content within the email.
You can check out a browser-based version of this using the HTML5 Canvas in action at http://tricedesigns.com/portfolio/sigCapture/ – Again, just enter a valid email address and use your mouse to sign within the signature area. When this is submitted, it will send an email to you containing the signature. However, this example requires that your browser supports the HTML5 Canvas tag.
You can check out the SignatureCapture code below, or check out the full project at https://github.com/triceam/Mobile-Signature-Capture/tree/master/html%20client. This class will also work in desktop browser applications that support the HTML5 canvas. I used Modernizr to determine whether touch events are supported within the client container (PhoneGap or desktop browser). The main application workflow is within application.js.
Also a note for Android users, the Canvas toDataURL() method does not work in Android versions earlier than 3.0. However, you can implement your own toDataURL() method for use in older OS versions using the technique in this link: http://jimdoescode.blogspot.com/2011/11/trials-and-tribulations-with-html5.html (I did not update this example to support older Android OS versions.)
Recently, I’ve been asked more than once which is better: AMF or JSON for AIR mobile applications. This post is to highlight some performance comparisons, and a sample testing application that I put together. First, it is important to know what both AMF and JSON are.
AMF
Action Message Format (AMF) is a compact binary format that is used to serialize
ActionScript object graphs. Once serialized an AMF encoded object graph may be used
to persist and retrieve the public state of an application across sessions or allow two
endpoints to communicate through the exchange of strongly typed data.
JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition – December 1999. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others.
Both AMF and JSON are compact serialization formats and provide efficient data transport. The main differences between the two formats are as follows:
AMF is a binary format that is not easily readable by humans, JSON is a text-based format that is easily readable.
AMF allows for serialization of strongly typed objects in transactions between the client and server, JSON only supports generic or loosely-typed objects.
Former Adobe Evangelist James Ward put together a suite of benchmarks comparing JSON, SOAP, and AMF that show comparable performance between AMF and JSON. Recently, AIR 3.0 and Flash Player 11 brought native JSON support, which greatly improves JSON parsing in Flash & AIR runtimes. This is a huge boost, especially for mobile applications that consume JSON data.
I put together a very basic test case where a mobile application makes requests of simple data objects from a ColdFusion CFC. In each test iteration, a request is made for 1, 10, 100, 1000, and 10000 value objects, in both AMF and JSON formats. The total round trip time from request to deserialization is measured and compared for each case, for a total of 5 iterations through each cycle. My findings are that AMF and JSON have comparable performance in smaller record sets. However, AMF seems to have better performance as data sets grow. In my test cases, the 1000+ record results were consistently faster using AMF. However, in smaller data sets, JSON was often faster (however not consistently, or by much of a margin). I tested these times on both an iPhone 4 and Motorolla Atrix, both running on the carrier networks (not over wifi).
Below is a video of the serialization testing application at work.
Here are a few screenshots of the application.
The Tests
For these tests I created two basic CFCs (ColdFusion Components). One is a simple data value object. The other CFC is a gateway to expose a remote service that returns the value objects to the client. I chose a ColdFusion CFC for this case b/c it can easily be serialized as AMF or JSON just by changing the endpoint used to consume the service.
Here is the service CFC used to return data to the client:
component {
remote array function getRecords(numeric records=1) {
var result = [];
for (var x = 0; x < records; x=x+1) {
var item = new SampleVO();
item.itemId = x;
ArrayAppend( result, item );
}
return result;
}
}
Obviously, this is a fictional data object with randomly generated values. However, it still represents a reasonable service payload for data serialization. By accessing the data via the ColdFusion Flex/Remoting gateway, you access the remote services via AMF3.
remoteObject = new RemoteObject("ColdFusion");
remoteObject.source = "com.tricedesigns.mobileTest.Services";
remoteObject.endpoint = "http://tricedesigns.com/flex2gateway/";
var token : AsyncToken = remoteObject.getRecords( RECORD_COUNT[ recordCountIndex ] );
token.addResponder( new mx.rpc.Responder( onAMFResult, onFault ) );
By accessing the data via an http endpoint, with returnformat=josn, you will invoke the same CFC remote method exposed as JSON.
httpService = new HTTPService();
httpService.url = "http://tricedesigns.com/com/tricedesigns/mobileTest/Services.cfc?method=getrecords&records=" + RECORD_COUNT[ recordCountIndex ] + "&returnformat=json";
var token : AsyncToken = httpService.send();
token.addResponder( new mx.rpc.Responder( onJSONResult, onFault ) );
The JSON-formatted data will look something like this:
In the mobile client application, I have a SerializationTestController class that handles all of the test logic and communications back and forth with the server. The time for each test is measured from immediately before the the request is made to the server, until after the data has been deserialized to an ArrayCollection. You can view the SerializationTestController class below:
package control
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.utils.getTimer;
import model.TestSummaryVO;
import model.TestVO;
import mx.collections.ArrayCollection;
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;
import mx.rpc.remoting.RemoteObject;
import views.SummaryView;
[Event(name="testStatusChange", type="control.TestUpdateEvent")]
[Event(name="testUpdate", type="control.TestUpdateEvent")]
public class SerializationTestController extends EventDispatcher
{
private var remoteObject : RemoteObject;
private var httpService : HTTPService;
private var _testing : Boolean = false;
private var testIndex : int = 0;
private var iterationIndex : int = 0;
private var recordCountIndex : int = 0;
private var testInstanceIndex : int = 0;
private var _results : ArrayCollection;
private var currentTest : TestVO;
public static const ITERATIONS : int = 5;
public static const RECORD_COUNT : Array = [1,10,100,1000,10000];
public static const TESTS : Array = [ TestVO.TYPE_AMF, TestVO.TYPE_JSON ];
public function SerializationTestController(target:IEventDispatcher=null)
{
super(target);
remoteObject = new RemoteObject("ColdFusion");
remoteObject.source = "com.tricedesigns.mobileTest.Services";
remoteObject.endpoint = "http://tricedesigns.com/flex2gateway/";
httpService = new HTTPService();
_results = new ArrayCollection();
}
[Bindable(event="testStatusChange")]
public function get testing ():Boolean
{
return _testing;
}
public function get results():ArrayCollection
{
return _results;
}
public function get chartResults() : ArrayCollection
{
var result : ArrayCollection = new ArrayCollection();
for ( var index : int = 0; index < ITERATIONS; index++ ) { var summaryVO : TestSummaryVO = new TestSummaryVO(); summaryVO.iteration = index+1; result.addItem( summaryVO ); } for each ( var vo : TestVO in results ) { summaryVO = result.getItemAt( vo.iteration ) as TestSummaryVO; if ( vo.type == TestVO.TYPE_AMF ) summaryVO[ "amfDuration" + SerializationTestController.RECORD_COUNT[ vo.recordIndex ] ] = vo.endTime - vo.startTime; else summaryVO[ "jsonDuration" + SerializationTestController.RECORD_COUNT[ vo.recordIndex ] ] = vo.endTime - vo.startTime; } return result; } public function startTest() : void { if ( _testing ) return; _testing = true; testIndex = 0; iterationIndex = 0; recordCountIndex = 0; testInstanceIndex = 0; updateTest(); dispatchEvent( new TestUpdateEvent( TestUpdateEvent.TEST_STATUS ) ); dispatchEvent( new TestProgressEvent( "STARTING TEST..." ) ); } private function completeTest() : void { _testing = false; dispatchEvent( new TestUpdateEvent( TestUpdateEvent.TEST_STATUS ) ); dispatchEvent( new TestProgressEvent( "TEST COMPLETE" ) ); } private function createTestVO() : void { currentTest = new TestVO(); currentTest.startTime = getTimer(); currentTest.index = testInstanceIndex; currentTest.iteration = iterationIndex; currentTest.recordIndex = recordCountIndex; currentTest.type = TESTS[ testIndex ]; } private function finalizeTestVO(error : Boolean = false) : void { if ( error ) currentTest.endTime = -1 else currentTest.endTime = getTimer(); _results.addItem( currentTest ); dispatchEvent( new TestUpdateEvent( TestUpdateEvent.TEST_UPDATE, currentTest ) ); dispatchEvent( new TestProgressEvent( "task completed in " + (currentTest.endTime - currentTest.startTime) + " milliseconds" ) ); currentTest = null; } private function updateTest() : void { if ( iterationIndex >= ITERATIONS )
return completeTest();
createTestVO();
if ( TESTS[ testIndex ] == TestVO.TYPE_AMF )
{
doAMFTest();
recordCountIndex ++;
if ( recordCountIndex >= RECORD_COUNT.length )
{
recordCountIndex = 0;
testIndex++;
}
}
else if ( TESTS[ testIndex ] == TestVO.TYPE_JSON )
{
doJSONTest();
recordCountIndex ++;
if ( recordCountIndex >= RECORD_COUNT.length )
{
recordCountIndex = 0;
testIndex = 0;
iterationIndex ++;
}
}
testInstanceIndex++;
}
private function doAMFTest() : void
{
dispatchEvent( new TestProgressEvent( "AMF Requesting " + RECORD_COUNT[ recordCountIndex ] ) );
var token : AsyncToken = remoteObject.getRecords( RECORD_COUNT[ recordCountIndex ] );
token.addResponder( new mx.rpc.Responder( onAMFResult, onFault ) );
}
protected function onAMFResult( event : ResultEvent ) : void
{
var result : ArrayCollection = event.result as ArrayCollection;
finalizeTestVO();
updateTest();
}
private function doJSONTest() : void
{
dispatchEvent( new TestProgressEvent( "JSON Requesting " + RECORD_COUNT[ recordCountIndex ] ) );
httpService.url = "http://tricedesigns.com/com/tricedesigns/mobileTest/Services.cfc?method=getrecords&records=" + RECORD_COUNT[ recordCountIndex ] + "&returnformat=json";
var token : AsyncToken = httpService.send();
token.addResponder( new mx.rpc.Responder( onJSONResult, onFault ) );
}
protected function onJSONResult( event : ResultEvent ) : void
{
var resultString : String = event.result as String;
var result : ArrayCollection = new ArrayCollection( JSON.parse( resultString ) as Array );
finalizeTestVO();
updateTest();
}
protected function onFault( event : FaultEvent ) : void
{
trace( event.fault.toString() );
finalizeTestVO(true);
updateTest();
}
}
}
Also, here is the TestVO value object that shows the information captured for each test:
package model
{
public class TestVO
{
public static const TYPE_JSON : String = "json";
public static const TYPE_AMF : String = "amf";
public var index : int;
public var iteration : int = 0;
public var startTime : int;
public var endTime : int;
public var type : String;
public var recordIndex : int;
public function TestVO()
{
}
}
}
Summary
Both JSON and AMF are acceptable serialization formats for mobile applications built with AIR. Both are compact serialization formats that minimize packet size. Both have native parsing/decoding by the AIR runtime. AMF will generally provide better performance for larger data sets. JSON *may* provide marginally better performance for small data sets. AMF also allows for strongly typed object serialization & deserialization, where JSON does not.
The answer to the question of “should I use AMF or JSON” is subjective… What kind of data are you returning, and how much data is it? Do you already have AMF services built? Do you already have JSON services built? Are the services consumed by multiple endpoints, with multiple technologies? Do you rely upon strongly typed objects in you development and maintenance processes? Both AMF and JSON are viable solutions for mobile applications.
Have you noticed when using twitter, google plus, or certain areas of facebook that when you scroll the page, it automatically loads more data? You don’t have to continually hit “next” to go through page after page of data. Instead, the content just “appears” as you need it. In this post we will explore a technique for making Flex list components behave in this exact way. As you scroll through the list, it continually requests more data from the server. Take a look at the video preview below, and afterwards we’ll explore the code.
The basic workflow is that you need to detect when you’ve scrolled to the bottom of the list, then load additional data to be displayed further in that list. Since you know how many records are currently in the list, you always know which “page” you are viewing. When you scroll down again, just request the next set of results that are subsequent to the last results that you requested. Each time you request data, append the list items to the data provider of the list.
First things first, you need to detect when you’ve scrolled to the bottom of the list. Here’s a great example showing how to detect when you have scrolled to the bottom of the list. You can just add an event listener to the list’s scroller viewport. Once you have a vertical scroll event where the new value is equal to the viewport max height minus the item renderer height, then you have scrolled to the end. At this point, request more data from the server.
One other trick that I am using here is that I am using conditional item renderers based upon the type of object being displayed. I have a dummy “LoadingVO” value object that is appended to the end of the list data provider. The item renderer function for the list will return a LoadingItemRenderer instance if the data passed to it is a LoadingVO.
Here it is up close, in case you missed it:
Here’s my InfiniteScrollList class:
package components
{
import model.InfiniteListModel;
import model.LoadingVO;
import mx.core.ClassFactory;
import mx.events.PropertyChangeEvent;
import spark.components.IconItemRenderer;
import spark.components.List;
import views.itemRenderer.LoadingItemRenderer;
public class InfiniteScrollList extends List
{
override protected function createChildren():void
{
super.createChildren();
scroller.viewport.addEventListener( PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler );
itemRendererFunction = itemRendererFunctionImpl;
}
protected function propertyChangeHandler( event : PropertyChangeEvent ) : void
{
//trace( event.property, event.oldValue, event.newValue );
if ( event.property == "verticalScrollPosition" )
{
if ( event.newValue == ( event.currentTarget.measuredHeight - event.currentTarget.height ))
{
fetchNextPage();
}
}
}
protected function fetchNextPage() : void
{
if ( dataProvider is InfiniteListModel )
InfiniteListModel( dataProvider ).getNextPage();
}
private function itemRendererFunctionImpl(item:Object):ClassFactory
{
var cla:Class = IconItemRenderer;
if ( item is LoadingVO )
cla = LoadingItemRenderer;
return new ClassFactory(cla);
}
}
}
You may have noticed in the fetchNextPage() function that the dataProvider is referenced as an InfiniteListModel class… let’s examine this class next. The InfiniteListModel class is simply an ArrayCollection which gets populated by the getNextPage() function. Inside of the getNextPage() function, it calls a remote service which returns data to the client, based on the current “page”. In the result handler, you can see that I disable binding events using disableAutoUpdate(), remove the dummy LoadingVO, append the service results to the collection, add a new LoadingVO, and then re-enable binding events using enableAutoUpdate(). Also, notice that I have a boolean _loading value that is true while requesting data from the server. This boolean flag is used to prevent multiple service calls for the same data.
Let’s take a look at the InfiniteListModel class:
package model
{
import flash.events.Event;
import flash.utils.setTimeout;
import mx.collections.ArrayCollection;
import mx.rpc.AsyncToken;
import mx.rpc.Responder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.RemoteObject;
public class InfiniteListModel extends ArrayCollection
{
private var _remoteObject : RemoteObject;
protected var _loading : Boolean = false;
public function get remoteObject():RemoteObject
{
return _remoteObject;
}
public function set remoteObject(value:RemoteObject):void
{
_remoteObject = value;
if ( _remoteObject )
getNextPage();
}
public function InfiniteListModel(source:Array=null)
{
super(source);
addItem( new LoadingVO() );
}
public function getNextPage() : void
{
if ( !_loading)
{
_loading = true;
trace( "fetching data starting at " + (this.length-1).toString() );
var token : AsyncToken = remoteObject.getData( this.length-1 );
var responder : Responder = new Responder( resultHandler, faultHandler );
token.addResponder( responder );
}
}
protected function resultHandler(event:ResultEvent):void
{
this.disableAutoUpdate();
if ( this.getItemAt( this.length-1 ) is LoadingVO )
this.removeItemAt( this.length-1 );
for each ( var item : * in event.result )
{
addItem( item );
}
addItem( new LoadingVO() );
this.enableAutoUpdate();
_loading = false;
}
protected function faultHandler(event:FaultEvent):void
{
trace( event.fault.toString() );
}
}
}
Now, let’s take a look at the root view that puts everything together. There is an InfiniteScrollList whose dataProvider is an InfiniteListModel instance. The InfiniteListModel also references a RemoteObject instance, which loads data from a remote server.
Let’s not forget the remote service. In this case, I’m calling into a very basic remote CFC that returns an Array of string values. You can see the code below:
Here’s another post that I originally wrote way back in 2006, when object oriented development was a newer concept to client-side web applications. Again, this post is still very relevant with Flex, AIR & ActionScript for mobile/web/desktop, so I decided to resurrect it from the old blog archive as well. Enjoy…
Understanding of OOP (Object Oriented Programming) is fundamental in being successful with the Flex framework and being able to get the most out of it. Developers who do not possess a computer science-related background may not be aware of the fundamental concepts that comprise OOP and how to apply them correctly, so here is a quick piece to help you out.
First, object oriented programming is a programming paradigm where your code is organized into logical objects, and each object has properties and methods. Each object contains similar and/or related functionality, and is organized into classes that logically represent and logically organize it’s functionality.
For Example:
Let’s say that we have a class “Automobile”. This class would contain the information and functions necessary for our application to use the Automobile class. We could have a numeric property for the number of wheels, the speed, and the direction (degrees on a compass). This class would also contain methods that control the actions of the Automobile object: accelerate, decelerate(break), turn, start engine, stop engine, etc… Our class would look something like this…
public class Automobile
{
public var speed : Number;
public var direction : Number;
public var numWheels : Number;
public function Automobile()
{ /* constructor */ }
public function accelerate() : void
{ /* speed up the automobile */ }
public function decelerate() : void
{ /* slow down the automobile */ }
public function turn( direction : Number ) : void
{ /* turn the automobile */ }
public function startEngine() : void
{ /* start the automobile engine */ }
public function stopEngine() : void
{ /* stop the automobile engine */ }
}
Ok, now that we have a brief explanation of what object oriented programming is, we can get into some more aspects of OOP: inheritance and interfaces.
Inheritance is a way to form new objects based on existing objects. When a class inherits from a base class, the new class extends the functionality of the base class, and can utilize public and protected properties and methods from that base class. Inheritance can be used to create different objects that utilize functions within the base class, so that the child classes all utilize the same code base. Inheritance can be used to extend the functionality of existing objects, and inheritance can also be used to override and/ or change functionality from the base class.
In Actionscript 3, you can access the parent class of your class by using the “super” keyword. For instance, calling the constructor of the parent class would use “super()”, where accessing a method of the parent class would use something like: “super.myMethodName()”. If a property of the parent class is created with public or protected access, you can access that property in the child class directly by the property name (you would use this.propertyName, not super.propertyName).
Now, Lets take our Automobile example and apply object-oriented inheritance. We already have a base Automobile class that covers the basic functionality. We can create child classes that extend the functionality of the Automobile.
public class SportsCar extends Automobile
{
public function SportsCar()
{
super();
}
override public function accelerate():void
{
/* we can override the accelerate function
so that it accelerates faster than the base
Automobile */
}
}
and…
public class Truck extends Automobile
{
public function Truck()
{
super();
}
public function tow() : void
{
/* we can add a tow function that
allows the Automobile class to tow
items. */
}
}
These classes extend the base functionality of the Automobile class, and therefore are instances of the Automobile class. If we have a function outside of the Automobile class, which takes an automobile as the parameter, both a SportsCar and Truck will work since they are both Automobiles. We could have a function such as the following: If we pass in a Truck Instance, and a SportsCar instance, both will work, and each will use the functionality of their specific class instead of the base Automobile class.
public function race( auto1 : Automobile, auto2 : Automobile ) : void
{
auto1.accelerate();
auto2.accelerate();
}
I’ll get into some more fine-grain details about inheritance later in this post… now, lets move on to interfaces…
Interfaces are slightly different than inheritance. An interface is a set of “rules” which an object must adhere to. The “rules” are actually method signatures that your class must implement. When we define an interface, we define method signatures that are required for classes that implement that interface. There is no actual code in an interface; it simply defines methods that must exist within your class. Your class that implements the interface must implement the code for the actual function. If you have multiple classes that implement an interface, those classes must have the same functions (only the ones required by the interface), but that is where the similarities of the two classes may stop. They could have completely different logic and properties within them… this is where inheritance and interfaces differ. Two objects that inherit from the same base class have a lot in common (properties and methods): two objects that implement the same interface only have those interface method signatures in common.
Let’s now make an Automobile Interface that defines the functions required to create an IAutomobile object (note the “I” stands for “interface”):
public interface IAutomobile
{
function accelerate() : void;
function decelerate() : void;
function turn( direction : Number ) : void;
}
We can use the IAutomobile interface to create objects (classes) that behave as Automobile objects. These classes do not necessarily inherit from each other and do not necessarily share any common properties.
public class Car implements IAutomobile
{
private var direction:Number;
private var speed:Number;
public function turn(direction:Number):void
{
this.direction = direction;
}
public function decelerate():void
{
this.speed++;
}
public function accelerate():void
{
this.speed--;
}
}
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas implements="IAutomobile"
xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
private var direction:Number;
private var speed:Number;
public function turn(direction:Number):void
{
this.direction = direction;
}
public function decellerate():void
{
this.speed++;
}
public function accellerate():void
{
this.speed--;
}
]]>
</mx:Script>
</mx:Canvas>
The previous two components both implement the IAutomobile interface, but have nothing else in common. One is simply a class that implements the interface, the other is a mxml component that implements the interface. The mxml component example extends the mx:Canvas component (the same thing could be done by creating an AS class that extends mx.containers.Canvas). Now, lets look at a function similar to the “race” function from earlier…
public function race( auto1 : IAutomobile, auto2 : IAutomobile ) : void
{
auto1.accelerate();
auto2.accelerate();
}
This example will work with either object that I have created because both objects implement the IAutomobile interface. They do not rely upon functions in the class hierarchy, just those that were implemented for this interface. You can also use multiple interfaces on classes that you create. Implementing multiple interfaces basically means that you are adding more required method signatures to your class, and you will have to implement these methods to satisfy each interface.
On the other hand, you cannot inherit from multiple classes. Some programming languages allow for multiple inheritance… ActionScript 3 does not support multiple inheritance (so i’ll stop there).
OK… enough of this rambling… What does this have to do with Flex?
Inheritance and interfaces are used extensively in AS3 to create the flex framework. All flex framework components that are rendered to the screen extend from the UIComponent class. AbstractService, DataSerice or EventDispatcher object implements the IEventDispatcher Interface. You may be using these concepts every day, but weren’t aware of them. Inheritance seems easier to take advanatage of at first… Lets say that you want to create several objects, all of which will have identical functions and variables. It is easy to see that you can create a base class that encapsulates all of the common functionality. You can then create a sub-classes that implement the differing functionality for each class.
When putting these concepts into real-world Flex applications you’ll need to get familiar with the following keywords:
extends
This is used when defining a child class from a parent class.
public class MyImage extends Image
implements
This is used when implementing an interface.
public class MyClass implements MyInterface
final
Classes and methods implented with “final” cannot be overridden.
final function myFunction() : void
static
The static keyword is used when creating variables or functions in a class that are specific to the class, not an instance. Static properties and methods do not require variable instantiation to be executed.
public static function myStaticFunction(): void
//to use it call it directly from ClassMyClass.myStaticFunction()
internal
This is used when creating a method or property that can be accessed by any object within the same package (namespace)
internal var foo : String;
override
This is used when creating a function that overrides another function from a parent class.
override public function myFunction() : void
private
This is used when creating methods or properties that are only available to the class where it is defined. A private variable cannot be accessed by outside classes or from descendant classes.
private var myPrivateValue : String;
protected
This is used when creating methods or properties that are only available to the class where it is defined and descendant classes. A protected variable cannot be accessed by outside classes.
protected var myProtectedValue : String;
public
This is used when creating properties and methods that are available to any class.
Here’s a quick tip for detecting device form factor (tablet vs phone) within your Flex mobile applications. First, get the stage dimensions for the screen size in pixels, then divide that by the applicationDPI (screen pixel density). This will give you the approximate size in inches of the device’s screen. I say “approximate” because the pixel densities are rounded to 160, 240, or 320, depending on the device. In my code, I make the assumption that if the landscape width is greater than or equal to 5 inches, then its a tablet. I used view states, but you can also layout components manually.
Check out the code below:
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:views="views.*"
resize="view1_resizeHandler(event)">
<fx:Script>
<![CDATA[
import mx.core.FlexGlobals;
import mx.events.ResizeEvent;
protected function view1_resizeHandler(event:ResizeEvent):void
{
var _width : Number = Math.max( stage.stageWidth, stage.stageHeight );
var _height : Number = Math.min( stage.stageWidth, stage.stageHeight );
_width = _width / FlexGlobals.topLevelApplication.applicationDPI;
_height = _height / FlexGlobals.topLevelApplication.applicationDPI;
//this will resolve to the physical size in inches...
//if greater than 5 inches, assume its a tablet
if ( _width >= 5 )
currentState = "tablet";
else
currentState = "phone";
}
]]>
</fx:Script>
<s:states>
<s:State name="tablet" />
<s:State name="phone" />
</s:states>
<views:PhoneView includeIn="phone"
width="100%" height="100%" />
<views:TabletView includeIn="tablet"
width="100%" height="100%" />
</s:View>
Here’s a post that I originally wrote way back in 2006, when Flex 2 was all the rage. No, seriously, Flex 2 was awesome – it is the base of today’s Flex framework, helped to revolutionize applications on the web, and heralded the “RIA” frenzy. The best part is that this post is still very relevant with Flex, AIR & ActionScript for mobile/web/desktop, so I decided to resurrect it from the old blog archive. Enjoy…
I’ve been asked several times, why would you use get/set functions instead of public variables in your flex components and classes? Well, there are some great things you can do with getters and setters that you can’t do with public variables. On the other hand, there are cases where public variables may be an easy choice. When using these functions and/or public variables, the code for the caller will be the same:
mycomponent.myValue = 1;
First, lets look at public variables…
[Bindable]
public var myValue : Number
Public variables are useful when there are no addional actions that need to take place when the value has been changed. If you change the value of “myValue”, the bindings will update and everything will be handled accordingly. The value will change, and anything bound to that value will change. In this case, there is no need to use getter/setter methods, keeping code simple and easy to implement.
Now, on to getters and setters…
[Bindable(event="myValueUpdated")]
public function set myValue (value:Number):void
{
_myValue = value;
dispatchEvent( new FlexEvent( "myValueUpdated" ) );
}
public function get myValue ():Number
{
return _myValue ;
}
private var _myValue : Number;
First I’ll explain the [Bindable(event="myValueUpdated")] statement: This indicates that the data binding to the getter’s value should be updated when the event of type “myValueUpdated” is dispatched.
You’ll notice that when the value is set, this event is dispatched, which would notify and components that are bound to this value. Using a binding event isn’t required for all getters and setters, however this approach can allow you to invoke binding events on the “getter” even if you don’t access the “setter” method.
Now, the rest… The code that I showed above is consumed in exactly the same way as a public property, but requires more code. The benefits of getter and setter functions are that they enable sequential code execution when the value is changed, and also enable inheritance in getter/setter methods.
This means that you can create your components so that specific functions are executed any time that the value is accessed using get and/or set functions.
Here’s an example:
[Bindable(event="myValueUpdated")]
public function set myValue (value:Number):void
{
_myValue = value;
numSets ++;
myFunction();
dispatchEvent( new FlexEvent( "myValueUpdated" ) );
}
public function get myValue ():Number
{
numGets ++;
myOtherFunction();
return _myValue ;
}
private var _myValue : Number;
private var numGets : Number = 0;
private var numSets : Number = 0;
In this example, every time the value is set, the numSets Number is incremented, and the myFunction() function is executed. Likewise, every time the value is accessed using the “get” method, the numGets Number is incremented, and the myOtherFunction() function is executed. There is no limit to what kind of code you can execute here. You can have it dispatch custom events, change styles, create new components, etc… This turns out to be very handy when creating custom Flex components.
As I mentioned earlier, getter/setter accessors also enable inheritance on “properties” of an object. This means that you can change the behavior of a getter/setter in descendant classes, while usage remains the same. A great example of this are the “get data” and “set data” accessor methods used throughout the Flex framework (and part of the IDataRenderer interface). You can override “get data” or “set data” methods to modify behaviors and/or return values, without changing how those methods are used.
Andrew is a Technical Evangelist for Adobe Systems. Disclaimer: This blog contains my personal thoughts and not those of my employer.
Tomorrow (Thurs) I'm presenting an "Introduction to Mobile Development with PhoneGap" at Towson University http://t.co/Qch6pD93 Come on out! 3 hours ago
Recent Comments