Skip to content

Putting all the pieces together

Dear Reader,

I have posted several posts on this blog that all derived from a sample app that I wrote to generate QR Codes using the Google API. So far I’ve only give you the individual pieces, this post is about pulling them all together and creating the application.

I have been working on this particular application since February. (I used it to generate the QR Code I sent to the lovely and talented Kathy for Valentine’s Day. (Yeah, I’m that much of a geek) If you are a regular reader then you will recognize some of the code and concepts that we have discussed previously.

The MXML can be found below this post, and a link to the FXP can be found just above it. If you have Flash Builder 4, grab the FXP and you can import it and see all the code.

Converting Flex Builder 3 to Flash Builder 4

This project actually started life as a Flex Builder 3 project. I did the normal Export/Import to get the project into Flash Builder 4 and this worked just fine. The code compiled and ran immediately. (Kudos to the Flash Builder 4 team for their work in backwards compatibility)

All was not perfect however. One of the cool new things about Flash Builder 4 is the Themes option. If you use the Spark components then themes will allow you to quickly change the look and feel of your application. If you right click on the project itself and then select Properties, you will notice a “Flex Theme” option.

From this dialog box you can choose from 13 different pre-defined themes and you can design your own.

Unfortunately, since I imported this app from Flex Builder 3, none of my visual controls were spark controls. The results of applying a theme was comical. Thanks to the code completion and some trial and error though, I was able to convert most of the controls over to spark. In most cases, the code completion actually told me “Don’t use X, use Y” which is a VERY NICE feature. The remaining MX controls did not have an obvious spark counterpart or the suggested counterparts didn’t work the way I wanted them to.

Setup and cleanup

A good portion of the Actionscript code in the MXML falls into 2 categories, either setup code or cleanup code.

Setup

[Bindable] protected var qrCode:QRCode = new QRCode();
 
var imageWindow:ImageWindow;
 
 protected function init():void
 {
         /*
         * add our event listeners
         */
         qrCode.addEventListener('imageCreated',this.displayImage);
         qrCode.addEventListener('imageFailed',this.cannotDisplayImage);
 } // protected function init():void

Here we create the QRCode class described in “Actionscript class to create QR Codes“. In the init() we add our event listeners for a successful image fetch and a fail.

Cleanup

		protected function destroy():void
		{
			try {
				imageWindow.close();
				imageWindow=null;
			} catch (e:Error) {
 
			}
		}

Here in destroy() we only do one thing, we close any open windows. Since this app only has one window, we only have to hard-code a close here. However, since the window may not be open, I’ve wrapped it in a try/catch so that if the window is not yet open, we handle the error. (Actually, we ignore the error)

The heart of the app

createImage() is the heart of the app. The MXML below it is just display code setting things up so that users can turn all the knobs and push all the buttons. Once they have everything the way they want, we call createImage().

the majority of the code is a big switch statement that puts together the right string to encode. The last one is the most difficult, the VCARD. [Side Note: Since I couldn’t find an Actionscript VCARD implementation, I may write one in a future post.]

       /*
        * Setup for the call
        */
       qrCode.width                = parseInt(imageWidth.text);
       qrCode.height               = parseInt(imageHeight.text);
       qrCode.margin               = margin.selectedIndex;
       qrCode.errorCorrectionLevel = errorCorrectionLevel.selectedIndex;
       qrCode.content              = contentToEncode; 			
       qrCode.imageType            = grpImageType.selectedValue.toString();
 
 
       /*
        * make the call
        */
       qrCode.createImage();

Once we know what we are encoding, the code above actually makes the call. We set the properties for the qrCode and the call qrCode.createImage().

In the init() we set event listeners for both a successful image retrieval and a failure. Since every developer knows their code doesn’t fail, let’s talk about the success.

protected function displayImage(event:Event):void
{
       var thisImage:Image = event.target.getImage();
       var winX:int = -1; 
       var winY:int = -1;
 
       try {
               winX=imageWindow.nativeWindow.x;
               winY=imageWindow.nativeWindow.y;
               imageWindow.close();
               imageWindow = null;
       } catch (e:Error) {
 
       }
       imageWindow = new ImageWindow(thisImage,grpImageType.selectedValue.toString());
       if (winX>-1) {
               imageWindow.open();
               imageWindow.nativeWindow.x=winX;
               imageWindow.nativeWindow.y=winY;
       } else {
               imageWindow.open();
       }
       return;
} // protected function displayImage(event:Event):void

Again, as with in destroy() we try to close the window. However, before we do so, we save off the X and Y of the window in case the user has moved it.

Then we create a new ImageWindow, passing in the image and the type of image to save. Finally, we position it. We have to position it after the call to open because before open is called, the nativeWindow property doesn’t exist.

The package store

Now that everything is working, we need to build it, package it and get it ready to ship. Up till now, when we have been compiling the application, we have been using the F11 key, which fired the debug version. That is why we can only have one instance running at a time. If you use CTRL-F11 to run your app, it won’t include the debug code and won’t try to attach itself to the debugger. However, to package it and ship it you have a few more steps.

Code signing

The last thing most AIR developers seem to think about is that they need a certificate to sign their code. It is a topic that serious application developers should tackle. I looked around at the web and found this page as the best reference for purchasing code signing certificates. The only one I could find that is not listed on that chart is GoDaddy. Unlike their SSL certificates, GoDaddy’s code signing certificates aren’t cheap. They start at $199/year.

No matter which service you go with, if you plan on distributing your application, you will need a certificate. For the purposes of this demonstration I will use a self signed certificate to package my application. This is not a recommended practice. (I’m just too cheap to pop for a cert for this tutorial)

To begin the export journey, click on the Project menu and select “Export Release Build”. This will bring up the first screen in the export wizard.

In this first screen we usually accept the defaults for everything. The one option of interest is the “Enable View Source” check box. Even though we are building an AIR application, we can still make our source code available for inspection. Checking the box enables the “Choose Source Files” button and you can decide to allow the user to browse the entire application or just certain files. When the user runs your application, a “View Source” option is added to the right click menu and if they choose it, they will see a code browser that looks like this:

As we discussed in the Code Signing section above, all AIR apps must be signed with a code signing certificate. Clicking “Next” on the wizard will bring up the code signing screen of the Wizard.

This is the screen where you select your certificate. If you are just testing, or distributing your application internally, you can choose to Create a certificate. While this will allow you to self-sign your application, when you install your users will see an ugly, scarry screen telling them that AIR via your operating system does not recognize the certificate of the publisher and thus cannot vouch for them.

This is generally not what you want when it comes to your customers.

You do have the option of creating an AIRI file. This is an intermediate file that you can hand off for signing. This is the option you would want if your company has a central authority that controls the signing certificates. They will sign the AIRI file using the command line utility adt and hand you back an AIR file ready to distribute.

Clicking “Next” will actually begin the export process. If you have warnings in your application (as the QRCodeMaker does) it will bring up a dialog box alerting you to those warnings and allowing you to cancel the process at that point and deal with them or continue on. Since ours is benign, I have chosen to ignore it.

The final step in the process is to choose which files get included the AIR or AIRI file. I assume this is to allow you to exclude common libraries and simply link to them. Our application is simple enough so that we just select the default which is the XML meta file and the generated SWF.

Clicking “Finish” will complete the export of your AIR or AIRI and you are ready to distribute or sign accordingly.

Conclusion

Developing desktop applications (and I assume mobile apps as wel) is never easy. Even trivial apps like QRCodeMaker require pre-planning to complete without several refacortings. The Flex framework, while verbose (as pointed out by my friends Nate Abele and Ed Finkler in the comments of the post “Actionscript class to create QR Codes“) is no more verbose than most modern frameworks. This is a problem I have with most modern frameworks for the languages with which I have worked. (Java, PHP and ActionScript) It is not necessarily a bad thing to be overly verbose but it does increase the learning curve on these frameworks.

The point of these posts has not been to show you best practices or even recommended practices. I myself am still discovering those. My point was to show you the process I went through to get this application running, my thought process behind some of the decisions I made, and to start a discussion.

I hope you will take the code I’ve shared with you, tinker with it, make it better and then share it with others.

Until next time,
I <3 |<
=C=

Disclosure: I work with Blue Parabola, Adobe is a customer of Blue Parabola.

Right Click to download the entire project
Completed (but self-signed) application.

QRCodeMaker.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
						xmlns:s="library://ns.adobe.com/flex/spark" 
						xmlns:mx="library://ns.adobe.com/flex/mx" 
						xmlns:net="flash.net.*" 
	                    creationComplete="init();" 
	                    width="250" 
	                    height="400"  
						showStatusBar="false"
						backgroundColor="#F0F0F0"
						closing="destroy()">
	<s:layout>
		<s:BasicLayout/>
	</s:layout>
 
	<fx:Declarations>
		<s:RadioButtonGroup id="grpImageType" />
	</fx:Declarations>
 
	<fx:Script>
	<![CDATA[
 
		import mx.controls.Alert;
		import mx.controls.Image;
		import mx.rpc.events.FaultEvent;
		import mx.rpc.events.ResultEvent;
		[Bindable] protected var qrCode:QRCode = new QRCode();
 
		var imageWindow:ImageWindow;
 
		 protected function init():void
		 {
			 /*
			 * add our event listeners
			 */
			 qrCode.addEventListener('imageCreated',this.displayImage);
			 qrCode.addEventListener('imageFailed',this.cannotDisplayImage);
		 } // protected function init():void
 
		protected function destroy():void
		{
			try {
				imageWindow.close();
				imageWindow=null;
			} catch (e:Error) {
 
			}
		}
		 protected function createImage():void
		 {
 
			var contentToEncode:String = ""; 
 
			/*
			 * Figure out which type of content we are encoding
			 */
			switch(tabBar.selectedIndex) {
		 		case 0:
		 			// TEXT
					contentToEncode  = this.textToEncode.text;
					break;
 
		 		case 1:
		 			// URL
					contentToEncode  = this.emailToEncode.text;
					break;
 
		 		case 2:
		 			// Email address
					contentToEncode  = 'MAILTO:'+this.urlToEncode.text;
					break;
 
		 		case 3:
		 			// vCard
		 		/*
				 * http://en.wikipedia.org/wiki/VCard
				 */
					contentToEncode = "BEGIN:VCARD\n" +
					                  "VERSION:3.0\n" +
					                  "N:"+txtLName.text+';'+txtFName.text+"\n" +
					                  "FN:"+txtFName.text+' '+txtLName.text+"\n" +
					                  "ADR:"+txtAddress.text+';'+txtCity.text+';'+txtState.text+';'+txtPostalCode.text+"\n" +
					                  "TEL:"+txtPhone.text+"\n" +
					                  "EMAIL:"+txtEmail.text+"\n" +
					                  "URL:"+txtUrl.text+"\n" +
					                  "END:VCARD\n";
					break;		 		
		 	}
 
			/*
			 * Setup for the call
			 */
			qrCode.width                = parseInt(imageWidth.text);
			qrCode.height               = parseInt(imageHeight.text);
			qrCode.margin               = margin.selectedIndex;
			qrCode.errorCorrectionLevel = errorCorrectionLevel.selectedIndex;
			qrCode.content              = contentToEncode; 			
			qrCode.imageType            = grpImageType.selectedValue.toString();
 
 
			/*
			 * make the call
			 */
			qrCode.createImage();
			return;			
		 } // protected function createImage():void
 
 
		 protected function displayImage(event:Event):void
		 {
			var thisImage:Image = event.target.getImage();
			var winX:int = -1; 
			var winY:int = -1;
 
			try {
				winX=imageWindow.nativeWindow.x;
				winY=imageWindow.nativeWindow.y;
				imageWindow.close();
				imageWindow = null;
			} catch (e:Error) {
 
			}
			imageWindow = new ImageWindow(thisImage,grpImageType.selectedValue.toString());
			if (winX>-1) {
				imageWindow.open();
				imageWindow.nativeWindow.x=winX;
				imageWindow.nativeWindow.y=winY;
			} else {
				imageWindow.open();
			}
		 	return;
		 } // protected function displayImage(event:Event):void
 
 
		 protected function cannotDisplayImage(event:FaultEvent):void
		 {
		 	Alert.show(event.toString());
		 } // protected function cannotDisplayImage(event:FaultEvent):void
 
	]]>
</fx:Script>
 
	<s:HGroup width="100%" height="100%">
		<mx:Spacer />
 
		<s:VGroup width="100%" height="100%">
			<mx:Spacer />
 
 
			<s:HGroup width="100%">
				<mx:Label text="Height"/>
				<mx:TextInput id="imageHeight" width="60" text="150" />
				<mx:Label text="Width"/>
				<mx:TextInput id="imageWidth" width="60" text="150" />
			</s:HGroup>
 
			<s:HGroup width="100%">
				<s:Label text="Quality" />
				<s:ComboBox width="50" 
							id="errorCorrectionLevel" 
							selectedIndex="1" 
							dataProvider="{qrCode.qualityArray}" />
 
 
				<s:Label text="Margin"/>
				<s:ComboBox  
					width="50"
					selectedIndex="4" 
					id="margin" 
					dataProvider="{qrCode.marginArray}"  />
			</s:HGroup>
 
			<s:VGroup gap="0" width="100%">
 
				<s:TabBar id="tabBar"  width="100%" dataProvider="{viewstack1}" />
 
				<mx:ViewStack 
					id="viewstack1" 
					width="100%" 
					height="225" >
					<s:NavigatorContent label="Text" width="100%" enabled="true" >
						<s:BorderContainer width="100%" height="100%" backgroundColor="#F0F0F0">
 
							<s:VGroup width="100%">
								<mx:Spacer  />
								<s:Label text="Enter Your Text Here" />
								<s:TextArea height="100%" width="95%" id="textToEncode" text=""/>
							</s:VGroup>
						</s:BorderContainer>
					</s:NavigatorContent>
 
					<s:NavigatorContent label="Email" width="100%" height="100%">
						<s:BorderContainer width="100%" height="100%" backgroundColor="#F0F0F0">
							<s:VGroup width="100%">
								<mx:Spacer />
								<s:Label text="Enter Your Email Here" />
								<s:TextInput width="95%" id="emailToEncode" text="mailto:" height="18"/>
							</s:VGroup>
						</s:BorderContainer>						
					</s:NavigatorContent>
 
					<s:NavigatorContent label="URL" width="100%" height="100%">
						<s:BorderContainer width="100%" height="100%" backgroundColor="#F0F0F0">
							<s:VGroup>
								<mx:Spacer />
								<s:Label text="Enter Your URL Here" />
								<s:TextInput width="95%" id="urlToEncode" text="" height="18"/>							
							</s:VGroup>
						</s:BorderContainer>						
					</s:NavigatorContent>
 
					<s:NavigatorContent label="VCard" width="100%" height="100%">
						<s:BorderContainer width="100%" height="100%" backgroundColor="#F0F0F0">
							<s:VGroup width="100%" height="100%"  gap="1" >
								<s:HGroup width="100%">
									<mx:Label text="First Name" width="75"/>
									<s:TextInput id="txtFName"/>
								</s:HGroup>
								<s:HGroup width="100%">
									<s:Label text="Last Name" width="75"/>
									<s:TextInput id="txtLName"/>
								</s:HGroup>
								<s:HGroup width="100%">
									<s:Label text="Address" width="75"/>
									<s:TextInput id="txtAddress"/>
								</s:HGroup>
								<s:HGroup width="100%">
									<s:Label text="City" width="75"/>
									<s:TextInput id="txtCity"/>
								</s:HGroup>
								<s:HGroup width="100%">
									<s:Label text="State" width="75"/>
									<s:TextInput id="txtState"/>
								</s:HGroup>
								<s:HGroup width="100%">
									<s:Label text="Postal Code" width="75"/>
									<s:TextInput id="txtPostalCode"/>
								</s:HGroup>
								<s:HGroup width="100%">
									<s:Label text="Phone" width="75"/>
									<s:TextInput id="txtPhone"/>
								</s:HGroup>
								<s:HGroup width="100%">
									<s:Label text="Email" width="75"/>
									<s:TextInput id="txtEmail"/>
								</s:HGroup>
								<s:HGroup width="100%">
									<s:Label text="URL"  width="75"/>
									<s:TextInput id="txtUrl"/>
								</s:HGroup>
							</s:VGroup>
						</s:BorderContainer>						
					</s:NavigatorContent>					
				</mx:ViewStack>
			</s:VGroup>
 
 
 
			<s:HGroup width="100%" horizontalAlign="center">				
				<s:RadioButton label="PNG" groupName="grpImageType" selected="true"/>
				<s:RadioButton label="JPG" groupName="grpImageType" />
			</s:HGroup>
			<s:Button label="Create QRCode" width="100%" id="drawImage" click="createImage()"/>
		</s:VGroup>
		<mx:Spacer />
 
	</s:HGroup>
 
</s:WindowedApplication>