In AIR 3.0, Adobe released a capability called Native Extensions. This allows you to directly extend the capabilities of the AIR Runtime on any platform supported by AIR (iOS, Android, Windows, OSX, etc…). In my consulting work for the NitroLM licensing and security project, I’ve had a chance to really dig into what it takes to develop a cross-platform native extension in C++.

I’m using Microsoft Visual C++ Express on Windows to create a .DLL and XCode 4 on OSX to create a .framework library. In this example, we’ll follow the code of one function in NitroLM called getMessages(). This function retrieves admin-generated messages from the NitroLM servers. For example, a developer might want to send his app users a message about the availability of a new version the next time they launch the software product.

On the AS3 side, you’ll need a class to handle the integration with the native libraries. For NitroLM, this class is called LicenseClient. You can view the full contents of this file on GitHub as the AS3 portion of the NitroLM library is open-source.

https://github.com/westbam/NitroLM/blob/master/nitrolm-air/src/com/nitrolm/LicenseClient.as

In the constructor of this class, we create the ExtensionContext and optionally call a function to turn on debugging in the native code.

public static var context:ExtensionContext;

/**
 * Create a new LicenseClient instance
 *
 * @param debug If enabled, the native library will write a file called nitrolm_debug.out
 */

public function LicenseClient(debug:Boolean = false)
{
  if(!context)
  {
    context = ExtensionContext.createExtensionContext("com.nitrolm.NitroLM", "");
  }
 
  if(context && debug)
  {
    context.call("enableDebug_air");
  }
}

Next, on the actionscript side, we call the getMessages() function to retrieve our list of messages. Since we don’t want to block our GUI thread waiting on an IO server call, we have to handle an event to retrieve the data. We listen for a LicenseClientEvent whose .data property will contain an Array of messages once it comes back.

public function getMessages(email:String, version:String, days:int):void
{
  context.addEventListener(StatusEvent.STATUS, handleStatusEvent);
  context.call("getMessages_air", email, version, days);
}

You’ll notice that we are handling another type of StatusEvent here. This is a message we’ll listen for internally that will let us know that the data is ready to be retrieved on the native side. When we handle this message in handleStatusEvent(), we can then make a second call into our native library to retrieve the message data. This 2-call approach prevents us from ever locking our AIR GUI thread waiting on the native side to complete.

private function handleStatusEvent(event:StatusEvent):void
{
  context.removeEventListener(StatusEvent.STATUS, handleStatusEvent);
  var req_type:int = new int(event.code);
  var response:int = new int(event.level);
  var lce:LicenseClientEvent = new LicenseClientEvent(LicenseClientEvent.LICENSE_RESPONSE, req_type, response);
 
  switch(req_type)
  {
    case NLMConstants.REQUEST_MESSAGES:
      if(response == NLMConstants.RESPONSE_OK)
      {
        lce.data = context.call("getMessages_data");
      }
    break;
  }
 
  dispatchEvent(lce);
}

The reason for the 2-call approach is that we’re using synchronous IO on the native side. If we didn’t spin up a new thread for the IO call on the native side, the AIR GUI would freeze every time we made a server call. To solve this, the first call is made to unpackage our AS3 parameters into native data types and spin up a new Thread to handle the IO. Our thread then tosses up a StatusEvent once it’s done receiving a response from the NitroLM server. A second call is then made to getMessages_data to convert the native response data into actionscript types we can use. Let’s take a look at the native code now.

We first need to define our native initializer, finalizer, and data functions in a header file.

/*
 * AIR_COMMANDS.H
 * Native entry point for Adobe AIR
 *
 * Copyright 2011 Simplified Logic, Inc.  All Rights Reserved.
 * Author: Andrew Westberg
 */


#ifndef __AIR_COMMANDS_H_
#define __AIR_COMMANDS_H_

#include "nlm_constants.h"
#include "nlm_commands.h"
#include "FlashRuntimeExtensions.h"

#ifdef __cplusplus
extern "C" {
#endif

...
FREObject getMessages_air(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]);
FREObject getMessages_data(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]);

void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions);
void ContextFinalizer(FREContext ctx);
void ExtInitializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer);
void ExtFinalizer(void* extData);

#ifdef __cplusplus
} //extern "C"
#endif

#endif //__AIR_COMMANDS_H_

Our initializer function is called when we first create the extension context. We need to tell it which functions the context has defined.

/*
 * AIR_COMMANDS.C
 * Exported functions for Adobe AIR (and related non-exported functions)
 *
 * Copyright 2011 Simplified Logic, Inc.  All Rights Reserved.
 *
 * Written by Andrew Westberg
 */


#include <stdlib.h>
#include "air_commands.h"
#include "CAsyncTask.h"
#include "storage.h"
#include "Debug.h"

#ifdef __cplusplus
extern "C" {
#endif

static CThread asyncThread;
static CAsyncTask asyncTask;

void ExtInitializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer)
{
  *ctxInitializer = &ContextInitializer;
  *ctxFinalizer = &ContextFinalizer;
}

void ExtFinalizer(void* extData)
{
}

void ContextFinalizer(FREContext ctx)
{
  gracefulShutdown();
}

void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions)
{
  asyncThread.m_gracefulShutdown = FALSE; //terminate thread hard at shutdown because we're inside a .DLL

  FRENamedFunction *func;

  *numFunctions = 50;
 
  func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * (*numFunctions));

  ...
 
  func[24].name = (const uint8_t*) "getMessages_air";
  func[24].functionData = NULL;
  func[24].function = &getMessages_air;

  func[46].name = (const uint8_t*) "getMessages_data";
  func[46].functionData = NULL;
  func[46].function = &getMessages_data;

  ...
 
  *functions = func;
}

#ifdef __cplusplus
} //extern "C"
#endif

In the function getMessages_air(), we parse out our input parameters and store them in our thread task and then spin up the task on our thread for processing. The threading library I’m using was developed by Walter Capers and can be found under the CodeProject article Creating a C++ Thread Class.

FREObject getMessages_air(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[])
{
    while(asyncTask.Status() == TaskStatusWaitingOnQueue || asyncTask.Status() == TaskStatusBeingProcessed)
    {
        //wait until our task is ready to process something new
        Sleep(10);
    }

    asyncTask.initialize(ctx, REQUEST_MESSAGES, argc, argv);

    if(asyncThread.Event(&asyncTask))
        Debug::logDebug("submitted getMessagesTask for background processing");
    else
        Debug::logDebug("ERROR submitting getMessagesTask for background processing");

    return NULL;
}

The asyncTask.initialize() method parses out our input parameters from actionscript and stores them in native object types so our thread can work with them. It’s important to run this code _before_ spinning up the thread because your thread WILL NOT have access to the FRE functions from Adobe. The only capabilities your thread has from Adobe is to toss up a StatusEvent using FREDispatchStatusEventAsync() when it’s done. You’ll also notice that you have to do a lot of error handling on just about every FRE function you call.

void CAsyncTask::initialize(FREContext ctx, const int requestType, int argc, FREObject *argv)
{
  //reset the task
  ThreadId_t id = 0;
  SetTaskStatus(TaskStatusNotSubmitted);
  Thread(&id);

  CAsyncTask::ctx = ctx;
  CAsyncTask::requestType = requestType;

  uint32_t len = -1;
  const uint8_t *email = 0;
  const uint8_t *version = 0;
  int32_t days = 0;
 
  switch(requestType)
  {
    case REQUEST_MESSAGES:
      if((res=FREGetObjectAsUTF8(argv[0], &len, &email)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FREGetObjectAsUTF8(argv[0], &len, &email) = %d", res);
        dispatchEvent(REQUEST_MESSAGES, RESPONSE_INTERNAL_CLIENT_ERROR);
        return;
      }
      strcpy(CAsyncTask::email, (const char*)email);

      if(argv[1] != NULL)
      {
        if((res=FREGetObjectAsUTF8(argv[1], &len, &version)) != FRE_OK)
        {
          Debug::logDebug("ERROR: FREGetObjectAsUTF8(argv[1], &len, &version) = %d", res);
          dispatchEvent(REQUEST_MESSAGES, RESPONSE_INTERNAL_CLIENT_ERROR);
          return;
        }
        strcpy(CAsyncTask::version, (const char*)version);
      }
      else
        strcpy(CAsyncTask::version, "");

      if((res=FREGetObjectAsInt32(argv[2], &days)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FREGetObjectAsUTF8(argv[2], &days) = %d", res);
        dispatchEvent(REQUEST_MESSAGES, RESPONSE_INTERNAL_CLIENT_ERROR);
        return;
      }
      CAsyncTask::days = days;
    break;
  }
}

Our CThread will call the Task() method of CAsyncTask once it starts processing. In this function, we make our synchronous IO call to the NitroLM server by calling the internal getMessages() method and dispatch an event once it’s complete.

void CAsyncTask::dispatchEvent(const int req_type, const int response)
{
  FREResult res;
  char code[5];
  char level[5];

  sprintf(code, "%d", req_type);
  sprintf(level, "%d", response);
  if((res=FREDispatchStatusEventAsync(ctx, (const uint8_t*)code, (const uint8_t*)level)) != FRE_OK)
  {
    Debug::logDebug("ERROR: FREDispatchStatusEventAsync(ctx, (const uint8_t*)code, (const uint8_t*)level) = %d", res);
    return;
  }
}

BOOL CAsyncTask::Task()
{
  ThreadId_t id = 0;
  Thread(&id);

  int ret;

  switch(CAsyncTask::requestType)
  {
    case REQUEST_MESSAGES:
      outMsgs = NULL;
      ret = getMessages(email, version, days, &outMsgs);
      dispatchEvent(REQUEST_MESSAGES, ret);
    break;
  }

  return TRUE;
}

Finally, we package the data as an Array of Objects when getMessages_data() is called from AIR.

FREObject getMessages_data(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[])
{
  int i;
  FREResult res;
  FREObject result;
  FREObject subject;
  FREObject body;
  FREObject message;
  FREObject thrownException;
  FREObject stackTrace;
  const uint8_t *stack;
  uint32_t len;

  if(asyncTask.outMsgs != NULL)
  {
    if((res=FRENewObject((const uint8_t*)"Array", 0, NULL, &result, &thrownException)) != FRE_OK)
    {
      Debug::logDebug("ERROR: FRENewObject((const uint8_t*)\"Array\", 0, NULL, &result, &thrownException) = %d", res);
      freeMessages(asyncTask.outMsgs);
      FRECallObjectMethod(thrownException, (const uint8_t*)"getStackTrace", 0, NULL, &stackTrace, NULL);
      FREGetObjectAsUTF8(stackTrace, &len, &stack);
      Debug::logDebug((const char*)stack);
      return NULL;
    }

    FRESetArrayLength(result, asyncTask.outMsgs->size);

    for(i=0;i<asyncTask.outMsgs->size;i++)
    {
      subject = 0;
      if(asyncTask.outMsgs->list[i].subject != NULL)
      {
        if((res=FRENewObjectFromUTF8(strlen(asyncTask.outMsgs->list[i].subject), (const uint8_t*)asyncTask.outMsgs->list[i].subject, &subject)) != FRE_OK)
        {
          Debug::logDebug("ERROR: FRENewObjectFromUTF8(strlen(asyncTask.outMsgs->list[i].subject), (const uint8_t*)asyncTask.outMsgs->list[i].subject, &subject) = %d", res);
          freeMessages(asyncTask.outMsgs);
          return NULL;
        }
      }
      if((res=FRENewObjectFromUTF8(strlen(asyncTask.outMsgs->list[i].msg), (const uint8_t*)asyncTask.outMsgs->list[i].msg, &body)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FRENewObjectFromUTF8(strlen(asyncTask.outMsgs->list[i].msg), (const uint8_t*)asyncTask.outMsgs->list[i].msg, &body) = %d", res);
        freeMessages(asyncTask.outMsgs);
        return NULL;
      }

      if((res=FRENewObject((const uint8_t*)"Object", 0, NULL, &message, &thrownException)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FRENewObject((const uint8_t*)\"Object\", 0, NULL, &message, &thrownException) = %d", res);
        freeMessages(asyncTask.outMsgs);
        FRECallObjectMethod(thrownException, (const uint8_t*)"getStackTrace", 0, NULL, &stackTrace, NULL);
        FREGetObjectAsUTF8(stackTrace, &len, &stack);
        Debug::logDebug((const char*)stack);
        return NULL;
      }

      if((res=FRESetObjectProperty(message, (const uint8_t*)"subject", subject, &thrownException)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FRESetObjectProperty(message, (const uint8_t*)\"subject\", subject, &thrownException) = %d", res);
        freeMessages(asyncTask.outMsgs);
        FRECallObjectMethod(thrownException, (const uint8_t*)"getStackTrace", 0, NULL, &stackTrace, NULL);
        FREGetObjectAsUTF8(stackTrace, &len, &stack);
        Debug::logDebug((const char*)stack);
        return NULL;
      }
      if((res=FRESetObjectProperty(message, (const uint8_t*)"body", body, &thrownException)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FRESetObjectProperty(message, (const uint8_t*)\"body\", body, &thrownException) = %d", res);
        freeMessages(asyncTask.outMsgs);
        FRECallObjectMethod(thrownException, (const uint8_t*)"getStackTrace", 0, NULL, &stackTrace, NULL);
        FREGetObjectAsUTF8(stackTrace, &len, &stack);
        Debug::logDebug((const char*)stack);
        return NULL;
      }

      if((res=FRESetArrayElementAt(result, i, message)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FRESetArrayElementAt(result, i, message) = %d", res);
        freeMessages(asyncTask.outMsgs);
        return NULL;
      }
    }

    freeMessages(asyncTask.outMsgs);
    return result;
  }

  return NULL;
}

Hopefully, this code helps you get a start on writing your own NativeExtensions for Adobe AIR. If you’d like more information about this particular NitroLM project, you can browse the GitHub projects and the NitroLM documentation.

Post to Twitter

Posted by Andrew, filed under AIR, as3, C++, security. Date: November 30, 2011, 1:48 pm | 10 Comments »

Flex-Mojos is a set of Maven plugins that allow you to do command-line builds for Flex projects. The real power comes from being able to leverage the power of Maven to do your entire build (front-end, back-end, unit testing, and integration testing) one fell swoop. It’s also very useful if you have a large complex project, or many developers on your team, where using a continuous integration tool such as Hudson would be useful.

Maven is an Apache open-source project that originated in the Java world at Sonatype. If you develop the back-end for your Flex projects in Java, you may already be familiar with Maven.

Adobe currently supports building from the command-line using Apache Ant. While this approach works for smaller projects, it’s difficult to work with large complex projects. Ant takes a very tactical approach to building a project. You give it lots of XML configuration and tell it virtually everything about your project(s). It will then run through your build script and do exactly what you told it to do.

Maven is FUNDAMENTALLY DIFFERENT!

Maven takes an approach of convention over configuration. If you follow the maven conventions (for example, putting your flex source code directory at /src/main/flex), then the Flex-Mojos plugin will just know where to find your code and what to do with it.

Adobe FlashBuilder does quite a a lot of configuration behind the scenes when building a Flex project. Things like building against framework RSLs instead of merging into code are handled seamlessly in FlashBuilder. Those aren’t really defaults in Flex-Mojos, so let’s take a look at what it takes Flex-Mojos to make a large project do some of the nice things FlashBuilder does for us.

The core of a Maven project is the pom.xml file. The Project Object Model files tweak what Maven does already by convention. If you don’t specify a config option, no big deal… Maven will just use its default.

In the project for 3dvideomap.com, I’m using the following technology stack. MySQL database, Hibernate for ORM to the tables, Spring Framework, GraniteDS for Flex Remoting, and Swiz for my Flex microarchitecture.

Folder structure is an important convention to Maven.

The main parent pom.xml file contains many of my variable properties that I’ll use later. It also tells Maven what child project modules to build.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.swiftmako.freshchamber</groupId>
    <artifactId>freshchamber-aggregator</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <name>FreshChamber Aggregator</name>

    <properties>
        <flash.major>10</flash.major>
        <flash.minor>2</flash.minor>
        <flash.revision>0</flash.revision>
        <flex.sdk.version>4.5.0.20967</flex.sdk.version>
        <flex.tlf.version>2.0.0.232</flex.tlf.version>
        <flex.osmf.version>1.0.0.16316</flex.osmf.version>
        <flex.mojos.version>4.0-beta-7</flex.mojos.version>
        <flexunit.version>4.0-rc-1</flexunit.version>
        <springframework.version>3.0.5.RELEASE</springframework.version>
        <graniteds.version>2.2.0.GA</graniteds.version>
        <hibernate.version>3.5.6-Final</hibernate.version>
        <aws.sdk.version>1.0.14</aws.sdk.version>
        <swiz.version>1.1.0</swiz.version>
        <googleanalytics.version>1.0.1.319</googleanalytics.version>
        <googlemap.version>1.20</googlemap.version>
    </properties>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.6</source>
                        <target>1.6</target>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

    <modules>
        <module>freshchamber-flex</module>
        <module>freshchamber-coupons</module>
        <module>freshchamber-webapp</module>
        <module>freshchamber-hibernate</module>
        <module>freshchamber-graniteds</module>
    </modules>
</project>

Let’s next take a look at the coupons project. This is a flex app related to the main 3dvideomap app in that it uses the same backend, but it just shows coupons at http://coupons.3dvideomap.com. It isn’t using any Flex Modules or other advanced features. It’s a simple Flex 4.5 app. Let’s take a look at it’s pom.

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>freshchamber-aggregator</artifactId>
        <groupId>com.swiftmako.freshchamber</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>com.swiftmako.freshchamber</groupId>
    <artifactId>freshchamber-coupons</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>swf</packaging>
    <name>freshchamber-coupons</name>
    <build>
        <sourceDirectory>src/main/flex</sourceDirectory>
        <testSourceDirectory>src/test/flex</testSourceDirectory>
        <plugins>
            <plugin>
                <groupId>com.savage7.maven.plugins</groupId>
                <artifactId>maven-external-dependency-plugin</artifactId>
                <version>0.5-SONATYPE-r116</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>resolve-external</goal>
                            <goal>install-external</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <createChecksum>true</createChecksum>
                    <skipChecksumVerification>false</skipChecksumVerification>
                    <artifactItems>
                        <artifactItem>
                            <groupId>com.adobe.flex.framework</groupId>
                            <artifactId>framework</artifactId>
                            <version>${flex.sdk.version}</version>
                            <packaging>swz</packaging>
                            <downloadUrl>http://fpdownload.adobe.com/pub/swz/flex/${flex.sdk.version}/{artifactId}_{version}.{packaging}</downloadUrl>
                        </artifactItem>
                        <artifactItem>
                            <groupId>com.adobe.flex.framework</groupId>
                            <artifactId>textLayout</artifactId>
                            <version>${flex.tlf.version}</version>
                            <packaging>swz</packaging>
                            <downloadUrl>http://fpdownload.adobe.com/pub/swz/tlf/${flex.tlf.version}/{artifactId}_{version}.{packaging}</downloadUrl>
                        </artifactItem>
                        <artifactItem>
                            <groupId>com.adobe.flex.framework</groupId>
                            <artifactId>osmf</artifactId>
                            <version>${flex.osmf.version}</version>
                            <packaging>swz</packaging>
                            <downloadUrl>http://fpdownload.adobe.com/pub/swz/flex/${flex.sdk.version}/{artifactId}_{version}.{packaging}</downloadUrl>
                        </artifactItem>
                        <artifactItem>
                            <groupId>com.adobe.flex.framework</groupId>
                            <artifactId>rpc</artifactId>
                            <version>${flex.sdk.version}</version>
                            <packaging>swz</packaging>
                            <downloadUrl>http://fpdownload.adobe.com/pub/swz/flex/${flex.sdk.version}/{artifactId}_{version}.{packaging}</downloadUrl>
                        </artifactItem>
                        <artifactItem>
                            <groupId>com.adobe.flex.framework</groupId>
                            <artifactId>charts</artifactId>
                            <version>${flex.sdk.version}</version>
                            <packaging>swz</packaging>
                            <downloadUrl>http://fpdownload.adobe.com/pub/swz/flex/${flex.sdk.version}/{artifactId}_{version}.{packaging}</downloadUrl>
                        </artifactItem>
                        <artifactItem>
                            <groupId>com.adobe.flex.framework</groupId>
                            <artifactId>mx</artifactId>
                            <version>${flex.sdk.version}</version>
                            <packaging>swz</packaging>
                            <downloadUrl>http://fpdownload.adobe.com/pub/swz/flex/${flex.sdk.version}/{artifactId}_{version}.{packaging}</downloadUrl>
                        </artifactItem>
                        <artifactItem>
                            <groupId>com.adobe.flex.framework</groupId>
                            <artifactId>spark</artifactId>
                            <version>${flex.sdk.version}</version>
                            <packaging>swz</packaging>
                            <downloadUrl>http://fpdownload.adobe.com/pub/swz/flex/${flex.sdk.version}/{artifactId}_{version}.{packaging}</downloadUrl>
                        </artifactItem>
                        <artifactItem>
                            <groupId>com.adobe.flex.framework</groupId>
                            <artifactId>advancedgrids</artifactId>
                            <version>${flex.sdk.version}</version>
                            <packaging>swz</packaging>
                            <downloadUrl>http://fpdownload.adobe.com/pub/swz/flex/${flex.sdk.version}/{artifactId}_{version}.{packaging}</downloadUrl>
                        </artifactItem>
                        <artifactItem>
                            <groupId>com.adobe.flex.framework</groupId>
                            <artifactId>sparkskins</artifactId>
                            <version>${flex.sdk.version}</version>
                            <packaging>swz</packaging>
                            <downloadUrl>http://fpdownload.adobe.com/pub/swz/flex/${flex.sdk.version}/{artifactId}_{version}.{packaging}</downloadUrl>
                        </artifactItem>
                        <artifactItem>
                            <groupId>com.adobe.flex.framework</groupId>
                            <artifactId>spark_dmv</artifactId>
                            <version>${flex.sdk.version}</version>
                            <packaging>swz</packaging>
                            <downloadUrl>http://fpdownload.adobe.com/pub/swz/flex/${flex.sdk.version}/{artifactId}_{version}.{packaging}</downloadUrl>
                        </artifactItem>
                    </artifactItems>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.sonatype.flexmojos</groupId>
                <artifactId>flexmojos-maven-plugin</artifactId>
                <version>${flex.mojos.version}</version>
                <extensions>true</extensions>
                <dependencies>
                    <dependency>
                        <groupId>com.adobe.flex</groupId>
                        <artifactId>compiler</artifactId>
                        <version>${flex.sdk.version}</version>
                        <type>pom</type>
                    </dependency>
                    <dependency>
                        <groupId>com.adobe.flex.compiler</groupId>
                        <artifactId>adt</artifactId>
                        <version>${flex.sdk.version}</version>
                        <scope>compile</scope>
                    </dependency>
                    <dependency>
                        <groupId>com.adobe.flex</groupId>
                        <artifactId>flex-fontkit</artifactId>
                        <version>${flex.sdk.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>com.adobe.flex</groupId>
                        <artifactId>rideau</artifactId>
                        <version>${flex.sdk.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>com.adobe.flex</groupId>
                        <artifactId>afe</artifactId>
                        <version>${flex.sdk.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>com.adobe.flex</groupId>
                        <artifactId>aglj40</artifactId>
                        <version>${flex.sdk.version}</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <id>html-wrapper</id>
                        <goals>
                            <goal>wrapper</goal>
                        </goals>
                        <configuration>
                            <templateURI>folder:/html-template</templateURI>
                            <parameters>
                                <swf>${project.build.finalName}</swf>
                                <width>100%</width>
                                <height>100%</height>
                                <version_major>${flash.major}</version_major>
                                <version_minor>${flash.minor}</version_minor>
                                <version_revision>${flash.revision}</version_revision>
                                <bgcolor>#FFFFFF</bgcolor>
                            </parameters>
                            <htmlName>index</htmlName>
                            <targetPlayer>${flash.major}.${flash.minor}.${flash.revision}</targetPlayer>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <configurationReport>true</configurationReport>
                    <sourceFile>index.mxml</sourceFile>
                    <swfVersion>11</swfVersion>
                    <compilerWarnings>
                        <warn-no-constructor>false</warn-no-constructor>
                    </compilerWarnings>
                    <metadata>
                        <title>Map Coupons</title>
                        <creators>
                            <creator>Andrew Westberg</creator>
                        </creators>
                        <publishers>
                            <publisher>Swift Mako Software Inc.</publisher>
                        </publishers>
                        <description>http://swiftmako.com</description>
                        <languages>
                            <language>EN</language>
                        </languages>
                    </metadata>
                    <keepAs3Metadatas>
                        <keepAs3Metadata>Bindable</keepAs3Metadata>
                        <keepAs3Metadata>Events</keepAs3Metadata>
                        <keepAs3Metadata>EventHandler</keepAs3Metadata>
                        <keepAs3Metadata>Inject</keepAs3Metadata>
                        <keepAs3Metadata>Autowire</keepAs3Metadata>
                        <keepAs3Metadata>Mediate</keepAs3Metadata>
                        <keepAs3Metadata>Dispatcher</keepAs3Metadata>
                        <keepAs3Metadata>PostConstruct</keepAs3Metadata>
                        <keepAs3Metadata>PreDestroy</keepAs3Metadata>
                    </keepAs3Metadatas>
                    <fonts>
                        <advancedAntiAliasing>true</advancedAntiAliasing>
                        <maxCachedFonts>20</maxCachedFonts>
                        <maxGlyphsPerFace>1000</maxGlyphsPerFace>
                        <managers>
                            <manager>flash.fonts.JREFontManager</manager>
                            <manager>flash.fonts.BatikFontManager</manager>
                            <manager>flash.fonts.AFEFontManager</manager>
                            <manager>flash.fonts.CFFFontManager</manager>
                        </managers>
                    </fonts>
                    <quality>0.90</quality>
                    <optimize>true</optimize>
                    <debug>false</debug>
                    <defaultBackgroundColor>0xFFFFFF</defaultBackgroundColor>
                    <themes>
                        <theme>${settings.localRepository}/com/adobe/flex/framework/spark/${flex.sdk.version}/spark-${flex.sdk.version}-theme.css</theme>
                    </themes>
                    <removeUnusedRsls>true</removeUnusedRsls>
                    <rslUrls>
                        <url>{artifactId}_{version}.{extension}</url>
                    </rslUrls>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.2</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>com.adobe.flex.framework</groupId>
                                    <artifactId>framework</artifactId>
                                    <version>${flex.sdk.version}</version>
                                    <type>swz</type>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                    <destFileName>framework_${flex.sdk.version}.swz</destFileName>
                                </artifactItem>
                                <artifactItem>
                                    <groupId>com.adobe.flex.framework</groupId>
                                    <artifactId>textLayout</artifactId>
                                    <version>${flex.tlf.version}</version>
                                    <type>swz</type>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                    <destFileName>textLayout_${flex.tlf.version}.swz</destFileName>
                                </artifactItem>
                                <!--
                                <artifactItem>
                                    <groupId>com.adobe.flex.framework</groupId>
                                    <artifactId>osmf</artifactId>
                                    <version>${flex.osmf.version}</version>
                                    <type>swz</type>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                    <destFileName>osmf_${flex.osmf.version}.swz</destFileName>
                                </artifactItem>
                                -->
                                <artifactItem>
                                    <groupId>com.adobe.flex.framework</groupId>
                                    <artifactId>rpc</artifactId>
                                    <version>${flex.sdk.version}</version>
                                    <type>swz</type>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                    <destFileName>rpc_${flex.sdk.version}.swz</destFileName>
                                </artifactItem>
                                <!--
                                <artifactItem>
                                    <groupId>com.adobe.flex.framework</groupId>
                                    <artifactId>charts</artifactId>
                                    <version>${flex.sdk.version}</version>
                                    <type>swz</type>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                    <destFileName>charts_${flex.sdk.version}.swz</destFileName>
                                </artifactItem>
                                <artifactItem>
                                    <groupId>com.adobe.flex.framework</groupId>
                                    <artifactId>mx</artifactId>
                                    <version>${flex.sdk.version}</version>
                                    <type>swz</type>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                    <destFileName>mx_${flex.sdk.version}.swz</destFileName>
                                </artifactItem>
                                -->
                                <artifactItem>
                                    <groupId>com.adobe.flex.framework</groupId>
                                    <artifactId>spark</artifactId>
                                    <version>${flex.sdk.version}</version>
                                    <type>swz</type>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                    <destFileName>spark_${flex.sdk.version}.swz</destFileName>
                                </artifactItem>
                                <!--
                                <artifactItem>
                                    <groupId>com.adobe.flex.framework</groupId>
                                    <artifactId>advancedgrids</artifactId>
                                    <version>${flex.sdk.version}</version>
                                    <type>swz</type>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                    <destFileName>advancedgrids_${flex.sdk.version}.swz</destFileName>
                                </artifactItem>
                                <artifactItem>
                                    <groupId>com.adobe.flex.framework</groupId>
                                    <artifactId>sparkskins</artifactId>
                                    <version>${flex.sdk.version}</version>
                                    <type>swz</type>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                    <destFileName>sparkskins_${flex.sdk.version}.swz</destFileName>
                                </artifactItem>
                                <artifactItem>
                                    <groupId>com.adobe.flex.framework</groupId>
                                    <artifactId>spark_dmv</artifactId>
                                    <version>${flex.sdk.version}</version>
                                    <type>swz</type>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                    <destFileName>spark_dmv_${flex.sdk.version}.swz</destFileName>
                                </artifactItem>
                                -->
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <!-- RSL dependencies -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>framework</artifactId>
            <version>${flex.sdk.version}</version>
            <type>swc</type>
            <scope>caching</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>textLayout</artifactId>
            <version>${flex.tlf.version}</version>
            <type>swc</type>
            <scope>caching</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>osmf</artifactId>
            <version>${flex.osmf.version}</version>
            <type>swc</type>
            <scope>caching</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>rpc</artifactId>
            <version>${flex.sdk.version}</version>
            <type>swc</type>
            <scope>caching</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>charts</artifactId>
            <version>${flex.sdk.version}</version>
            <type>swc</type>
            <scope>caching</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>mx</artifactId>
            <version>${flex.sdk.version}</version>
            <type>swc</type>
            <scope>caching</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>spark</artifactId>
            <version>${flex.sdk.version}</version>
            <type>swc</type>
            <scope>caching</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>advancedgrids</artifactId>
            <version>${flex.sdk.version}</version>
            <type>swc</type>
            <scope>caching</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>sparkskins</artifactId>
            <version>${flex.sdk.version}</version>
            <type>swc</type>
            <scope>caching</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>spark_dmv</artifactId>
            <version>${flex.sdk.version}</version>
            <type>swc</type>
            <scope>caching</scope>
        </dependency>

        <!-- standard dependencies -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>flex-framework</artifactId>
            <version>${flex.sdk.version}</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>com.adobe.flexunit</groupId>
            <artifactId>flexunit</artifactId>
            <version>${flexunit.version}</version>
            <type>swc</type>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.graniteds</groupId>
            <artifactId>granite-essentials-swc</artifactId>
            <version>${graniteds.version}</version>
            <type>swc</type>
            <scope>internal</scope>
        </dependency>
        <dependency>
            <groupId>org.swizframework</groupId>
            <artifactId>swiz-framework</artifactId>
            <version>${swiz.version}</version>
            <classifier>sparkonly</classifier>
            <type>swc</type>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

The first thing you’ll notice is that I’m using the maven-external-dependency-plugin to download all of the flex framework .swz files and store them in my local maven repository. They need to be in my local repository so I can copy them to the output folder later on.

I’m then executing the flex-mojos-maven-plugin. You’ll notice that I specify an execution to run the html-wrapper goal. In that goal, I specify a template folder so it will use the html template generated by FlashBuilder instead of the Flex-Mojos default. You’ll may also notice that I’m _NOT_ specifying an execution for the compile-swf goal. That’s because this happens automatically by convention. I only need to specify parameters for what I’m changing from the defaults.

When you run your build using “mvn clean install”, Flex-Mojos will spit out a list of what RSLs are required for your .swf to run. Use this list and comment out any unneeded ones in the maven-dependency-plugin: copy goal. This ensures that you only copy those RSL swz files to the output folder that your swf actually needs.

Let’s next look at a more complex example. The freshchamber-flex project is another Flex app, but this one uses a Flex Module. The Admin Module only shows up in the app when an end user accesses it from mapdashboard.com. When a normal user accesses the map, we don’t want to have the overhead of all of that configuration and editing code, so we put it into a module and only load it on the occasion that an admin or advertiser wants to edit the database.

The only changed section is to our configuration where we tell it to compile a module and optimize it for our main swf. We also have to force certain RSLs to be loaded because they’re _only_ used by the Admin module.

                    <modules>
                        <module>
                            <sourceFile>com/swiftmako/freshchamber/views/AdminViewModule.mxml</sourceFile>
                            <optimize>true</optimize>
                            <finalName>AdminViewModule</finalName>
                            <destinationPath>com/swiftmako/freshchamber/views</destinationPath>
                        </module>
                    </modules>
                    <removeUnusedRsls>true</removeUnusedRsls>
                    <rslUrls>
                        <url>{artifactId}_{version}.{extension}</url>
                    </rslUrls>
                    <forceRsls>
                        <rsl>${settings.localRepository}/com/adobe/flex/framework/mx/${flex.sdk.version}/mx-${flex.sdk.version}.swc</rsl>
                        <rsl>${settings.localRepository}/com/adobe/flex/framework/charts/${flex.sdk.version}/charts-${flex.sdk.version}.swc</rsl>
                        <rsl>${settings.localRepository}/com/adobe/flex/framework/sparkskins/${flex.sdk.version}/sparkskins-${flex.sdk.version}.swc</rsl>
                    </forceRsls>

So, that’s the basics of making flex-mojos behave a bit like FlashBuilder. Below you’ll find an archive of all the various poms in my project. It will cover things like generating AS3 classes from Java classes for use in GraniteDS remoting. Post comments below if you have any questions.

POMs.zip

Post to Twitter

Posted by Andrew, filed under as3, Flex, Flexbuilder, Java, Maven. Date: May 19, 2011, 11:31 am | 5 Comments »

I’ve noticed that at times FlashBuilder and in my case, the FlashBuilder plugin for Eclipse seems to randomly lose its ability to work. In the past, I’ve had to completely rebuild my workspace and re-import all of my projects. I’ve also seen a number of posts about how you should

1.) shutdown flashbuilder
2.) in your workspace/.metadata/.plugins folder, delete the com.adobe.flexbuilder.codemodel folder
3.) restart flashbuilder

Unfortunately, this one didn’t work for me. If it works for you, great. My problem seemed to be some type of conflict with either the subclipse plugin, or the M2Eclipse plugin. The following worked for me. Hopefully this is useful to you.

1.) shutdown flashbuilder
2.) in your workspace/.metadata/.plugins folder, delete any folder containing “subversion”, or “maven” in its name.
3.) restart flashbuilder

Hopefully this helps you out. If not, best of luck rebuilding your workspace.

Post to Twitter

Posted by Andrew, filed under as3, Flex, Flexbuilder. Date: March 23, 2011, 10:48 am | 2 Comments »

Nothing is more annoying than loading up a full browser flex app that has a login screen… It looks like it’s ready so you start typing only to realize that doesn’t let you type anything in. Usually, this is because your SWF file doesn’t have the browser focus. Even if you’ve set the focus to a text input field in ActionScript, you can still fall victim to this issue.

I’d like to share a few techniques I’ve used to ensure that the Flex app is able to get the browser’s focus immediately after loading.

First, you need to ensure that your wmode is set to opaque inside your html script. This isn’t required for all browsers, but in my layman’s testing, Chrome seemed to need it. If you’re using swfobject (the default for a FlashBuilder 4 app), customizing your html template will look something like this.

        <script type="text/javascript" src="swfobject.js"></script>
        <script type="text/javascript">
            <!-- For version detection, set to min. required Flash Player version, or 0 (or 0.0.0), for no version detection. -->
            var swfVersionStr = "${version_major}.${version_minor}.${version_revision}";
            <!-- To use express install, set to playerProductInstall.swf, otherwise the empty string. -->
            var xiSwfUrlStr = "${expressInstallSwf}";
            var flashvars = {};
            var params = {};
            params.quality = "high";
            params.bgcolor = "${bgcolor}";
            params.allowscriptaccess = "sameDomain";
            params.allowfullscreen = "true";
            params.wmode = "opaque";
            var attributes = {};
            attributes.id = "${application}";
            attributes.name = "${application}";
            attributes.align = "middle";
            swfobject.embedSWF(
                "${swf}.swf", "flashContent",
                "${width}", "${height}",
                swfVersionStr, xiSwfUrlStr,
                flashvars, params, attributes);
            <!-- JavaScript enabled so display the flashContent div in case it is not replaced with a swf object. -->
            swfobject.createCSS("#flashContent", "display:block;text-align:left;");
        </script>

Next, you need to run a bit of javascript code to set the focus to your Flex app after it has loaded. There are a number of techniques floating around to do this from your html template, but I prefer the tried and true method of using ExternalInterface from inside your Flex app’s applicationComplete handler. That way, you’re absolutely sure everything has loaded and you’re ready to give yourself the focus.

protected function applicationCompleteHandler(event:FlexEvent):void
{
    //set focus to this app using javascript
    if(ExternalInterface.available)
    {
        ExternalInterface.call("eval", "document.getElementById('" + ExternalInterface.objectID + "').tabIndex=0");
        ExternalInterface.call("eval", "document.getElementById('" + ExternalInterface.objectID + "').focus()");
    }
}

Setting the tabIndex to a value is another one of those quirky Chrome requirements. I found it on a bug report for Google Chrome and it seems to work for me. http://code.google.com/p/chromium/issues/detail?id=27868#c15 I’ve tested this code in Firefox, IE, and Chrome. It should work in Safari as well, but I haven’t tested that one.

Post to Twitter

Posted by Andrew, filed under as3, Flex. Date: December 30, 2010, 6:42 pm | 16 Comments »

When you layer Bitmap images on top of each other in Flash/Flex/AIR, an interesting thing happens. Mouse events are captured by the top-most image and aren’t passed through to anything underneath. Even when hovering over, or clicking on transparent areas, the events just don’t get through. Moses over at MosesSupposes created an InteractivePNG solution that does some interesting mouse handling and hit detection.

In the above example (click the image, right-click to view src), I have two transparent png bitmaps layered on top of each other. On the left side, I’m using the standard BitmapImage tag. When you hover over any part of the images (even transparent areas), you’ll notice that you get the SNOWFLAKE! tooltip. Since the snowflake is on top, it is capturing all mouse activity so nothing gets through to Santa underneath.

On the right, I’m using BitmapSprite. When you have a Sprite that is rendered using graphics drawing, mouse events are only captured in areas where the graphics object has physically been drawn on. BitmapSprite is designed to take an input Bitmap, convert it into 1-pixel tall rectangle bitmap fills that are drawn to the sprite’s graphics object. In this manner, any transparent pixel doesn’t capture mouse events and everything is passed down to objects beneath. When you hover over a snowflake pixel, you get the tooltip SNOWFLAKE!, and when you hover over santa, you get a SANTA! tooltip. With this solution, we’re also caching the result as a bitmap so we only pay the rendering penalty once. No mouse-handling or hit detection kung-fu necessary. Nifty!

Post to Twitter

Posted by Andrew, filed under as3, Flex. Date: December 1, 2010, 12:07 pm | 10 Comments »

On a project I’m working on, I need to have semi-transparent containers to hold controls over top of screen-filling bright and colorful background. While I’m not 100% satisfied with it, I think it will suffice for the time being.

I was originally going for an effect similar to the window borders in Windows 7 where the background image is blurred when viewed through the glass. I couldn’t find an easy way to do that effect in Flex 4. If you know of a way, please comment below.

Live Demo – Right-Click to View Source

Post to Twitter

Posted by Andrew, filed under as3, Flex. Date: July 31, 2010, 11:03 pm | 2 Comments »

I recently had an opportunity to give a talk at 360|Flex along with Randy Troppmann. Slides and code examples are posted below. Video of the session will come sometime in the unforeseen future as they have many hours of other sessions to post-process before getting to mine.

Presentation PDF (warning: large)

MileageBuddy (viewsrc enabled)

HeatmapExample (viewsrc enabled)

Test GPX file

Post to Twitter

Posted by Andrew, filed under 360 Flex, AIR, as3, Data, Degrafa, Flex, GPS. Date: March 11, 2010, 5:04 pm | No Comments »

dual_dragmanagers

The new Flex 3.4 SDK has been released. Since Adobe still hasn’t gotten around to fixing this bug, I’ve again taken it upon myself to release my MonkeyPatched code for Flex 3.4 that allows you to use the Flex DragManager along side the Native DragManager in an AIR app. You can grab the code from SVN from my google code site.

http://code.google.com/p/andrewwestberg/source/checkout

Post to Twitter

Posted by Andrew, filed under AIR, as3, Flex, MonkeyPatch. Date: August 28, 2009, 12:53 pm | No Comments »

oh_example

I’ve been using the ObjectHandles flex library project for quite some time. It’s used in the Nitro-LM Admin tool to give us resizeable horizontal menus.

In the new version of ObjectHandles2, we have the ability to use Sprites as the drag handles to resize and move objects around the screen. I was recently made a committer on the project and have added some functionality to allow Degrafa to be used to draw the handles and rotate them along with the object. Check out Example7 that highlights this new functionality.

ObjectHandles2Example View Source is enabled

Post to Twitter

Posted by Andrew, filed under as3, Degrafa, Flex. Date: August 25, 2009, 7:17 pm | 6 Comments »

Off and on, I’ve been working making the Flex DragManager work in AIR. I’ve been vocal in trying to get SDK-13983 fixed. This feature is really useful because you have no control over the DragProxy’s alpha value in AIR because it’s controlled by the operating system. If all operating systems defaulted to 0.5 alpha, all would be well, but OSX thinks that the drag proxy should be completely opaque and Windows thinks it should be 0.5.

The MonkeyPatch to fix this issue was posted to my Google code repository. Its only problem was that the mouse cursor for drag feedback disappeared when you were using a child window. I’ve added one small patch to UIComponent which fixes this issue. Grab the latest code if this is of interest to you. Let me know here if you have trouble with it.

http://code.google.com/p/andrewwestberg/

Post to Twitter

Posted by Andrew, filed under AIR, as3, MonkeyPatch. Date: March 16, 2009, 12:03 pm | 2 Comments »

« Previous Entries