Monday, March 24, 2008

AIR / Browser API example

AIR / Browser API example

The other day I blogged about a wrapper class that I wrote to handle the AIR / Browser integration API.  Below is an example of another class that I wrote that uses that wrapper class.  It's capable of detecting two different AIR applications, installing them, and running them.  It relies on the wrapper class to do all the heavy lifting, but I thought this might be a good example to help people get started.

package com.agileagenda.web
{

import com.roguedevelopment.air.AIRBrowserRuntime;

import com.roguedevelopment.air.AIRBrowserRuntimeEvent;

import flash.events.IOErrorEvent;

public class InstalledApplications

{

public static function get instance() : InstalledApplications

{

if( _instance == null ) { _instance = new InstalledApplications(); }

return _instance;

}

protected static var _instance:InstalledApplications = null;

protected var api:AIRBrowserRuntime;  

protected static const PUBLISHER_ID:String = "F49A4D8DF78A1FEE7A3BE440DC11BAB18D922274.1";

protected static const TRACKER_ID:String =  "com.agileagenda.AgileTracker";

protected static const MAIN_APP_ID:String = "com.agileagenda.AgileAgenda";


protected static const AGILE_AGENDA_INSTALL:String =  "http://www.agileagenda.com/download/AgileAgenda.air";

protected static const AGILE_TRACKER_INSTALL:String = "http://www.agileagenda.com/download/AgileTracker.air";


[Bindablepublic var isReady:Boolean=false;

[Bindablepublic var hasAIR:Boolean;

[Bindablepublic var agileAgendaVersion:String = "";

[Bindablepublic var agileTrackerVersion:String = "";

public function InstalledApplications()

{

  api = new AIRBrowserRuntime();

  api.addEventListener(AIRBrowserRuntimeEvent.READY, onReady );

  api.addEventListener(IOErrorEvent.IO_ERROR, onAirFail );

  api.load();

}


protected function onAirFail(event:IOErrorEvent) : void

{

  trace( event.text );

}

protected function onReady(event:AIRBrowserRuntimeEvent ) : void

{

  hasAIR = api.getStatus() == 'installed';

  isReady = true;

  api.addEventListener(AIRBrowserRuntimeEvent.APP_VERSION_RESULT, onTrackerVersionResult );

  api.getApplicationVersion( TRACKER_ID, PUBLISHER_ID );

}

protected function onTrackerVersionResult(event:AIRBrowserRuntimeEvent) : void

{

  trace("Tracker version installed: " + event.detectedVersion );

  agileTrackerVersion = event.detectedVersion

  api.removeEventListener(AIRBrowserRuntimeEvent.APP_VERSION_RESULT, onTrackerVersionResult );

  api.addEventListener(AIRBrowserRuntimeEvent.APP_VERSION_RESULT, onAppVersionResult );

  api.getApplicationVersion( MAIN_APP_ID, PUBLISHER_ID );

}

protected function onAppVersionResult(event:AIRBrowserRuntimeEvent ) : void

{

  trace("Application version installed: " + event.detectedVersion );

  agileAgendaVersion  = event.detectedVersion

  api.removeEventListener(AIRBrowserRuntimeEvent.APP_VERSION_RESULT, onAppVersionResult );

}

public function launchAgileAgenda( args:Array ) : void

{

  api.launchApplication( MAIN_APP_ID, PUBLISHER_ID, args );

}


public function launchAgileTracker( args:Array ) : void

{

  api.launchApplication( TRACKER_ID, PUBLISHER_ID, args );

}

public function installAgileAgenda(args:Array) : void

{

  api.installApplication(AGILE_AGENDA_INSTALL, "1.0", args );

}


public function installAgileTracker(args:Array) : void

{

  api.installApplication(AGILE_TRACKER_INSTALL, "1.0", args );

}


}

}

Labels: ,

Sunday, March 23, 2008

Interacting with an AIR app from a browser based app

Interacting with an AIR app from a browser based app
We've all seen the AIR installation badges that let you install an AIR application from a website. But the API exposed to do that lets you do more than just a simple badge. I've been working on a web-based service for AgileAgenda. One of the components of that is to manage the list of files you've saved to the service and be able to open those in the desktop AIR application. So right from within the online Flex based app it sure would be nice to detect if the application is installed, give the user the option to install, and then launch it for them and automatically open the desired file. Something like this...
To implement that we need to:
  1. Detect whether or not the application is installed.
  2. Display the version number if it is. (Disable the "Open schedule in..." button if it's not)
  3. When clicking on the "Open" link, launch the application with a few parameters so it know what to open.
  4. When clicking on the "Install" link, install the application and pass a few parameters so it know what to open when it launches directly after the install.
Doing things like that falls outside of the normal AS3 web development API. But Adobe provides a swf that you can load at runtime and them make calls to. You can read all about that over here: http://livedocs.adobe.com/air/1/devappshtml/help.html?content=distributing_apps_3.html.
But there's an annoying thing with that. You need to load up that remote swf and then access it like a dynamic object. No compiler-time type checking. Not the standard event mechanism. And no code-completion from within Flex Builder.
Luckily for you, I went through and made a wrapper class that you can download from here:
This will handle the loading of the remote swf, dispatching events when it loads (or fails to load) and then wraps the API for the entire process. There's nothing revolutionary in there, it's mostly takendirectly from that livedocs page above.
So now that you have that handy-dandy wrapper class, lets look at how to actually get something done.
Setting up your AIR application to work with your website (Important)
First thing, go into your AIR application's -app.xml file and make sure allowBrowserInvocation is set to true. By default it's set to false.

<allowBrowserInvocation>true</allowBrowserInvocation>

If you don't do this, you won't even be able to query version information on your application. But, be careful. By doing this you're letting any website launch your AIR application from a web page. You need to be careful in how much your app trusts command line arguments passed to it. For instance, you should never pass a file to delete on the command line.

Now that you've set allowBrowserInvocation to true, create a new .air file and post that to your website somewhere.

Using the wrapper class to interact with your application

Either open an existing Flex or Actionscript project in FlexBuilder and put the source to the AIRBrowserRuntime.as somewhere that the compiler will find it. Somewhere in your main application, create an AIRBrowserRuntime object, set some event listeners, and call the load() method to load the air.swf file from Adobe's servers.

var api:AIRBrowserRuntime;

...

api = new AIRBrowserRuntime();

api.addEventListener(AIRBrowserRuntimeEvent.READY, onReady );

// Optional: api.addEventListener(IOErrorEvent.IO_ERROR, onAirFail );

api.load();

Once the READY event is dispatched, you can start calling methods to query application versions, install apps, or launch applications.

Checking if AIR is installed

To see if the AIR runtime is installed, call the getStatus() method. It will return one of three values.

available - The AIR runtime can be installed on this computer but currently it is not installed

unavailable - The AIR runtime cannot be installed on this computer.

installed - The AIR runtime is installed on this computer.

Example:

switch( api.getStatus() )

{

case "available": trace("AIR is available, but not installed."); break;

case "unavailable": trace("AIR is not available for this computer."); break;

case "installed": trace("AIR is already installed on this computer."); break;

}

Checking the version of an installed application

The getApplicationVersion method will check to see if a given application is installed and give you the version if it is. This method operates asynchronously so you have to create an event listener before you call it. If you look at the method signature...

public function getApplicationVersion(applicationID:String, publisherID:String) : void

You'll see that it takes an applicationID and a publisherID. The applicationID is just value of the <id> tag of your application descriptor (the -app.xml file). It'll probably be something like this, but you need to make sure to make each application unique:

<id>com.agileagenda.AgileTracker</id>

The publisherID is a little trickier to find. That doesn't get assigned until you actually sign your .AIR file with your code-signing key. I know of 2 ways of finding it, but there's probably an easier way (please leave a comment if you know how).

Warning about Publisher ID:  If you change the key your sign your app with, the publisher ID will change.

Option 1: Get it at runtime.

In your application's main MXML throw up an Alert message with the publisher ID. Then build a .air, install the .air and run it. Copy & Paste the result.  Example:

<?xml version="1.0" encoding="utf-8"?>

<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"

    creationComplete="init()" >

  <mx:Script>

  <![CDATA[

    protected function init() : void 

    {

      Alert.show( nativeApplication.publisherID);

    }

  ]]>

  </mx:Script>

</mx:WindowedApplication>

Which results in something resembling:

It's hard to see in that picture, but the publisher ID is: F49A4D8DF78A1FEE7A3BE440DC11BAB18D922274.1 as it wraps to two lines.
Option 2: Get it after install
This is probably a bit easier, but I'm not 100% sure where this directory goes on a windows box.
On OSX (maybe on Windows, I'm not sure exactly where) the application storage directory that gets created for your application has the publisher ID appended to it. So if you look in your user directory -> Library -> Preferences, you should see a directory that starts with your application ID and ends with your publisher ID.
As you can see there, we get the same publisher ID value as Option #1.
On to checking installed version
Now that you have your application ID and your publisher ID you can make a call from your web application to the API to request the installed version of your AIR application.  This operates asynchronously so you'll need an event listener for the result.
Example:

api.addEventListener(AIRBrowserRuntimeEvent.APP_VERSION_RESULT, onTrackerVersionResult );

api.getApplicationVersion( "com.agileagenda.AgileTracker", "F49A4D8DF78A1FEE7A3BE440DC11BAB18D922274.1" );

...

protected function onTrackerVersionResult(event:AIRBrowserRuntimeEvent) : void

{

trace("Tracker version installed: " + event.detectedVersion );

}

The event.detectedVersion that comes back will be the value in your application descriptor version tag.  Mine looks like this:

<version>v1</version>

So the trace output looks like this:


Tracker version installed: v1

Launching an installed AIR application

Assuming you followed along in the previous section, you have an Application ID and a Publisher ID ready. To launch an AIR app from a web app you just call

api.launchApplication( "com.agileagenda.AgileTracker", "F49A4D8DF78A1FEE7A3BE440DC11BAB18D922274.1" );

launchApplication has a third, optional, parameter called arguments and is typed as an array. Anything you pass into that will be passed along to the application in an INVOKE event. This way you can pass information from the web application to the AIR application. Example:
api.launchApplication( "com.agileagenda.AgileTracker", "F49A4D8DF78A1FEE7A3BE440DC11BAB18D922274.1", ["Argument1","Argument2" ] );
Be careful, the arguments are very restrictive in what characters can be passed. Things like dashes, percent signs, underscores, all cause a runtime exception on the web application. You're probably safest only using alphanumeric characters. I haven't found a comprehensive list of what characters are or are not valid. If someone has that, please leave a comment below.  This was done for security reasons.
Installing an AIR application
To install an AIR application, all you need to do is call the installApplication with the absolute URL to the .air file you want to install. Example:
api.installApplication("http://www.agileagenda.com/download/AgileTracker.air");

This will take care of installing the AIR runtime, installing the application, and launching it for the first time. installApplication has two more optional parameters. The AIR runtime that the application requires, and arguments that can be passed to the application for it's first run. Example:

api.installApplication("http://www.agileagenda.com/download/AgileTracker.air","1.0",["Arg1","Arg2"]);
That's all there is to it.
So that's it, pretty simple, huh? You might also want to look into using a LocalConnection to do realtime communication between your web-app and your air-app once the air-app has been launched. Recap of the links:
Wrapper Class:
Adobe's Documentation:
My Blog (where any updates to the wrapper class will be posted)
The actual air.swf that does all the heavy lifting (This URL is wrong in some of the docs!)

http://airdownload.adobe.com/air/browserapi/air.swf


Labels: ,

Monday, January 07, 2008

AgileAgenda - New Version

Posted a new version of AgileAgenda over the weekend.  Fixed a few minor bugs, redesigned the opening screens, and put in our new logo.  The entire initial user experience should be better now.   Here's a quick glimpse of what it looks like now:



Labels: , , ,

Saturday, December 15, 2007

AIR Badge installer + swfobject + ExpressInstall

I've put together a page to install AgileAgenda using SWFObject with the ExpressInstall feature and the AIR Installation Badge.

This means people with a Flash Player less than 9.0.115 should be able to first upgrade their flash player, and then use the easy badge installation method for AIR + the application. I gave it a try on Firefox + Safari on OSX and IE + Firefox on WinXP, all of them with a 9.0.47 flash player and it all seemed to work well. The code also displays a message suggesting people install Flash player or install the AIR application manually if they don't have any version of Flash.

I've put together a small archive of the necessary files to make this work. It contains files from the swfobject guys released under the MIT license, and you can consider anything I wrote to make this work also under that license (which allows you to pretty much use it any way you like).

Thanks go to the swfobject guys, they really made this a no-brainer on how to implement!

Hope this helps some people.

Labels: , , ,

Friday, December 14, 2007

AgileAgenda - New Version

For those of you new here, AgileAgenda is a project scheduling application built on Adobe AIR.

There's a new version of AgileAgenda now available on the website.

It's been updated for AIR Beta 3, and includes a lot of bug fixes and minor enhancements, with a couple large feature improvements thrown in for good measure. You can read the list on the project blog.

The biggest change for me is file open/save dialogs work on OSX Leopard! Yay.

Labels: , ,

Monday, October 22, 2007

AIR Mime type

I've been having some problems with some browsers downloading my .air apps as .zip files, even replacing the file extension. I figured it was a Mime type problem, and sure-enough...



This is from the AIR release notes:

Setting the mime type in your Web Server for AIR applications

In order for client browsers to recognize an AIR application when being downloaded, the Web Server hosting the AIR applications needs to map the following MIME Content Type “application/vnd.adobe.air-application-installer-package+zip” to the Apollo extension “.air”. For example, for an Apache Web Server you will add to the AddType section: AddType application/vnd.adobe.air-application-installer-package+zip .air


So for Apache, just add this to your config:

AddType application/vnd.adobe.air-application-installer-package+zip .air

I had some weird problems with the badge-installer not working on a couple OSX machines, I wonder if this will fix it.




Labels:

Monday, October 01, 2007

Native code in air apps?

I just watched a demo of running the PDF reader in an AIR app. One interesting caveat is the reader starts with every installed reader plugin the user has. I wonder if this means we could use that mechanism for running native code by writing a reader plugin...

Labels:

Wednesday, September 05, 2007

AirDerbyEntries++

Last night I submitted agile agenda for the AIR derby. I wasn't going to originally since I didn't think I had a chance in hell, but then I got thinking. Even making some kind of entry-list might generate some good publicity. With beta starting within the next week or two, I could use a little press.

Labels: , ,

Monday, September 03, 2007

AgileAgenda facelift progress

I've been feverishly working on some of the UI elements of AgileAgends that have been sorely lacking in recent revisions. I've gotten a lot closer to something I'm proud to admit to doing. Here's a couple screenshots.







Go see a full listing of screenshots.


Also, we're still accepting beta apps...

Labels: , ,

Wednesday, August 29, 2007

XRay Viewer

A lot of the time a designer gives me a swf, and I want to know how they structured it. I have two options.

1) Open up the Flash IDE and explore it
2) Add an instance of it to my project, and then use XRay to explore it.

Now, I have a third option. I can open it up in the XRayViewer AIR application I just put together and then use XRay to look through it.

XRayViewer.air

All this little app does is host the XRay connector and let you load a local swf (File->Open). Then it displays the swf with some simple controls to play/stop/advance/back.




But the real power is you can go off to the XRay interface and look through that loaded swf!

Labels: , , ,

Saturday, August 25, 2007

AgileAgenda progress

With the release of AlivePDF, I've started on the printed reports of the project, arguably one of the most important features of software like this. Here is a sample report for the first few hours of working on this stuff. I'm quite pleased with how easily and quickly it was to develop that. It needs a lot of work visually, but I think it's actually a useful report. When printing these out you can choose which sections of the report will go in. Eventually I want to get the outline/bookmark PDF features working to make it easy to jump around in the document. I still have to tackle printing out the gantt style chart, but that's for another day. (Note on sample... that project is an amazingly simple project not really needing software like this. I've also been using this software for a project I've been working on at my "real job" and that is actually useful)

I've also been working with a young designer from France on some of the visual aspects of the software. He's been making icons for a while now and agreed to let me use some of them in my project. On top of that he did a mockup on a screen that took my thoughts on design to a completely new direction. He's working on a project of his own as well, I'll make sure to post about it when it's ready. Here's an example of what the software might look like come release time:



Even that look still has a couple rounds of revisions left, but it's getting closer.

We're still accepting beta applications for anyone interested in trying this stuff out in the first round of beta.

Labels: , ,

Sunday, August 12, 2007

Image/screenshot viewer with hotspots

Yesterday, I found myself wanting to post some screenshots of a project I'm working on, but I wanted to be able to define "hotspots" on the image that would display more information upon mousing over them. Here is what I came up with:

http://www.agileagenda.com/tour/FeatureDisplay.html

But that's not the cool part. The cool part is in only a couple hours, I was able to extend that viewer into an AIR application that could edit the XML config file. Here's some screenshots of that tool:


I'm using ObjectHandles to handle moving & resizing the hotspots. Then I'm using a simple RichTextEditor to edit the text that is displayed upon mouseover.

This tool needs some work, but I could see it being useful to other people someday.

Labels: ,