I’ve been working on a startup company called MileTrack GPS for the past 3 years. It’s designed to eliminate the pain and frustration of business mileage tracking.

I developed this first and foremost for myself. I’m a software development consultant and was tracking my mileage by hand while driving around town working for various companies. Writing down mileage in a log book was painful.

Back when I started this project, Adobe Flex was the pinnacle of Rich Internet applications. This website spawned from those glory days. While the flash platform will live on and still be useful for many applications, for my purposes, it’s deprecated. The maps APIs for Flex I’ve relied on are steadily becoming deprecated. As such, I’ve taken on the task of moving to a JavaScript framework called Angular JS.

The new webapp is about 90% completed. You can view a demo of it below.

Angular JS has some similar features to Flex in that it does databinding. It’s not as easy to use or straightforward as the Flex implementation, but for my purposes, it works. I also now have access to the latest Google JS maps apis. This is especially important for being able to run the webapp on tablet computers.

If you have a chance, check out Angular JS

Post to Twitter

Posted by Andrew, filed under Flex, GPS, JavaScript. Date: January 13, 2014, 3:36 pm | 1 Comment »

I use Moniker for hosting some of my domains. I also use them to forward certain email addresses to my gmail account. I noticed that every time I added a new email forward address, Moniker would change the nameservers for the domain back to their own nameservers. This was frustrating because I use Amazon Route 53 to host all of my DNS records. Email forwarding works by simply adding the moniker MX record into my Route 53 record. Apparently, Moniker doesn’t understand how this stuff works.

moniker1

moniker2

moniker3

moniker4

moniker5

moniker6

moniker7

Post to Twitter

Posted by Andrew, filed under Uncategorized. Date: July 19, 2013, 5:29 pm | No Comments »

10  Sep
Architexa

I recently received an e-mail from Christopher Deschenes at Architexa. I don’t usually do blog posts based on requests, but I decided to give Architexa a go. It looks to be free for small teams of 3 or less. It’s a tool based around an Eclipse plugin for helping you quickly understand and documenting your Java code.

It seems very useful for helping get a new developer up to speed with a codebase. It allows you to visually explore a Layered Diagram to quickly see what packages depend on others. You can also drill down to the individual class level to see what it’s dependencies are. Thicker arrows indicate more dependencies.

You can also open up a Class Diagram to get more details on how dependencies flow. In the below example, I wanted to drill down to see what error handling I had for the Captcha system in miletrack. You can easily jump over to the code once you find what you’re looking for.

For an easy way to see what calls and objects are returned from those calls, you can use the sequence diagram.

Post to Twitter

Posted by Andrew, filed under Java. Date: September 10, 2012, 8:35 am | No Comments »


What follows is a very brief synopsis of my experience developing an app for Android and then porting it to iOS. Please remember that my opinions are but a single data point. After you’ve remembered that, feel free to promptly forget it and start the flame wars in the comments section. If my coding skills are lacking and I’m missing a simple way to do something I claim is impossible, feel free to let me know that too.

The App

MileTrack GPS is an app that tracks mileage driven for IRS reimbursement purposes. It uploads any trips you take to the miletrackgps.com website where you can then categorize, map, and label trips. It then allows you to generate PDF reimbursement reports. I’ve been marketing the app as “Pain free mileage tracking” because I want the app to be completely brainless. You get into the car, plug in your phone, drive, arrive, unplug. Nothing else, the app should just do its thing.

Challenge 1: Launch App in Background when device is plugged in

For the app to perform properly, it needs to launch and run in the background when it’s plugged in. I can then start monitoring location services for movement so I can start tracking a new trip.

Android

In Android, this is accomplished by first registering a receiver in AndroidMainfest.xml inside the application tag.

<receiver android:name=".PowerBroadcastReceiver">
  <intent-filter>
    <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
    <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
  </intent-filter>
</receiver>

Then, in the PowerBroadcastReceiver java class, I can fire up an Intent to my GPS service that will notify it of the event so it can take proper action. Either start listening for movement and thus beginning a new trip, or uploading the current trip if power was just disconnected.

package com.swiftmako.miletrack;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;

public class PowerBroadcastReceiver extends BroadcastReceiver {

  private static String TAG = "MileTrackGPS";
   
  public static SharedPreferences pm = null;

  @Override
  public void onReceive(Context context, Intent intent) {
    Log.d(TAG, intent.getAction());
       
    if(pm == null) {
      pm = PreferenceManager.getDefaultSharedPreferences(context);
    }
       
    boolean automaticTracking = pm.getBoolean("automaticTracking", false);
       
    if(automaticTracking) {
      if(intent.getAction().equals("android.intent.action.ACTION_POWER_CONNECTED")) {
        Intent serviceIntent = new Intent("com.swiftmako.miletrack.intent.action.GPS");
        serviceIntent.putExtra("POWER_STATE", "CONNECTED");
        context.startService(serviceIntent);
      }
      else {
        Intent serviceIntent = new Intent("com.swiftmako.miletrack.intent.action.GPS");
        serviceIntent.putExtra("POWER_STATE", "DISCONNECTED");
        context.startService(serviceIntent);
      }
    }
  }

}

iOS

In Apple’s infinite wisdom, they decided to not allow apps to be launched based on a system event like connecting a charger. You can be notified if you’re already running, but if you’re not running, no such luck. According to this apple doc, it does look like if you add the UIBackgroundModes key voip, your app can be launched into the background after system startup. This would most likely get our app rejected since we’re not really a Voip app. Another way to do it is by requiring the user’s phone be jailbroken. Doesn’t seem like a good solution to me though.

WINNER: Android

Challenge 2: Track movement using GPS

The most important part of our app is getting good data from the GPS device built into the smartphone. If we get inaccurate data, then the purpose of our app is rendered impotent.

Android

In our AndroidManifest.xml, we first ask for permission to use GPS.

  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

We then ask for a LocationManager instance in our GPSService’s onCreate() method.

  lm =  (LocationManager)getSystemService(LOCATION_SERVICE);

We can then create a LocationListener to notify us of location events once every second. The accuracy we get from this method is good. We can rate “fair” as anything under 15 meters of accuracy and record those points. Everything above that should be ignored.

  private void startGPSListener() {
    if(lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
      locationListener = new LocationListener() {
       
        public void onStatusChanged(String provider, int status, Bundle extras) {
        }
       
        public void onProviderEnabled(String provider) {
          sendClientMessages(MSG_ACCURACY_CHANGED, getText(R.string.gps_accuracy_srch));
        }
       
        public void onProviderDisabled(String provider) {
          sendClientMessages(MSG_ACCURACY_CHANGED, getText(R.string.gps_accuracy_off));
        }
       
        public void onLocationChanged(Location location) {
          if(location.hasAccuracy()) {
            if(location.getAccuracy() < 5) {
              sendClientMessages(MSG_ACCURACY_CHANGED, getText(R.string.gps_accuracy_excellent));
            }
            else if(location.getAccuracy() < 10) {
              sendClientMessages(MSG_ACCURACY_CHANGED, getText(R.string.gps_accuracy_good));
            }
            else if(location.getAccuracy() < 15) {
              sendClientMessages(MSG_ACCURACY_CHANGED, getText(R.string.gps_accuracy_fair));
            }
            else if(location.getAccuracy() < 20) {
              sendClientMessages(MSG_ACCURACY_CHANGED, getText(R.string.gps_accuracy_poor));
            }
            else {
              sendClientMessages(MSG_ACCURACY_CHANGED, getText(R.string.gps_accuracy_bad));
            }
           
            writeTrackPoint(location);
          }
        }
      };
     
      lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, locationListener);
     
      isListening = true;
     
          // Display a Toast notification about us starting.
          Toast.makeText(this, R.string.gps_service_started, Toast.LENGTH_SHORT).show();
         
          listenerStartTime = System.currentTimeMillis();
         
          Log.d(TAG, "startGPSListener");
    }
    else {
      //show notification about GPS being disabled
      sendClientMessages(MSG_GPS_DISABLED, null);
    }
  }

iOS

In Obj-C, we first setup our info.plist file to tell it what kind of capabilities we expect the device to have.

  <key>UIRequiredDeviceCapabilities</key>
  <array>
    <string>gps</string>
    <string>location-services</string>
    <string>armv7</string>
  </array>

Next, we get the CLLocationManager and tell it to start updating the location. The manager in iOS differs from Android in that you can’t specify a set period of time to get updates.

    //initialize location tracking
    locationManager = [[CLLocationManager alloc] init];
   
    //set ourselves as the locationmanager delegate
    [locationManager setDelegate:trackViewController];
   
    //get all results
    [locationManager setDistanceFilter:kCLDistanceFilterNone];
       
    //be as accurate as possible no matter the battery life
    [locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
   
    [locationManager startUpdatingLocation];

We have to do quite a bit of work to get decent locations out of iOS. Even then, the speeds (both calculated by us OR calculated by iOS) are old and inaccurate. Most of this code was taken from a stackoverflow post in trying to get decent data out of iOS. We can get decent location tracking out of this. Speed tracking remains inaccurate though.

- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)ignoredOldLocation
{
    static int attempts = 0;
    static CLLocation *oldLocation = nil;
    BOOL locationChanged;
   
    NSDate* eventDate = newLocation.timestamp;
    NSTimeInterval howRecent = abs([eventDate timeIntervalSinceNow]);
    attempts++;
   
    double hdop = [newLocation horizontalAccuracy];
   
    if(hdop < 0) {
        //invalid fix
        return;
    }
    if(hdop < 10) {
        [self setAccuracy:@"Excellent"];
    }
    else if(hdop < 50) {
        [self setAccuracy:@"Good"];
    }
    else if(hdop < 75) {
        [self setAccuracy:@"Fair"];
    }
    else if(hdop < 150) {
        [self setAccuracy:@"Poor"];
    }
    else {
        [self setAccuracy:@"Bad"];
    }
   
    if((newLocation.coordinate.latitude != oldLocation.coordinate.latitude) || (newLocation.coordinate.longitude != oldLocation.coordinate.longitude))
        locationChanged = YES;
    else
        locationChanged = NO;
   
#ifdef __i386__
    //Do this for the simulator since location always returns Cupertino
    if (howRecent < 5.0)
#else
    // Here's the theory of operation
    // If the value is recent AND
    // If the new location has slightly better accuracy take the new location OR
    // If the new location has an accuracy < 50 meters take the new location OR
    // If the attempts is maxed (5) AND the accuracy < 75 AND the location has changed, then this must be a new location and the device moved
    // so take this new value even though it's accuracy might be worse
    if ((howRecent < 5.0) && ( (newLocation.horizontalAccuracy < (oldLocation.horizontalAccuracy - 10.0)) || (newLocation.horizontalAccuracy < 50.0)
                              || ((attempts >= 5) && (newLocation.horizontalAccuracy <= 75.0) && locationChanged)))
#endif            
    {
        attempts = 0;
       
        currentLocation = newLocation;
       
        if(lastTrackedLocation == nil && isLoggingStarted)
        {
            lastTrackedLocation = newLocation;
        }
       
        double distance;
        if(oldLocation != nil) {
            distance = [newLocation distanceFromLocation:oldLocation];
            double time = [[newLocation timestamp] timeIntervalSinceDate:[oldLocation timestamp]];
           
            if(distance > 0.0 && time > 0.0) {
                double speedMs = distance / time;
                double mph = speedMs * MS_TO_MPH;
               
                NSLog(@"Timestamp: %@, lat: %f, lon: %f, speed: %f mph, hdop: %f", [newLocation timestamp], [newLocation coordinate].latitude, [newLocation coordinate].longitude, mph, hdop);
               
                //this value is usually old and not accurate
                //[self setCurrentSpeed:[newLocation speed]];
               
                if(mph >= 0.0) {
                    [self setCurrentSpeed:mph];
                }
            }
        }

        oldLocation = newLocation;
       
        if(lastTrackedLocation != nil && isLoggingStarted) {
            distance = [newLocation distanceFromLocation:lastTrackedLocation];
            if(distance > MINIMUM_TRACKPOINT_DISTANCE) {
                totalDistance += distance * METERS_TO_MILES;
                [self distanceChanged];
                lastTrackedLocation = newLocation;
                [self writeTrackPoint: newLocation];
            }
        }
        else if (isLoggingStarted) {
            lastTrackedLocation = newLocation;
            [self writeTrackPoint: newLocation];
        }
    }
    else {
        NSLog(@"howRecent: %f, HDOP: %f, locationChanged: %d", howRecent, hdop, locationChanged);
    }
}

WINNER: Android

Challenge 3: Notify the User

Our app needs a way to notify the user that we’re tracking a trip, that we’re uploading a trip, and that we’ve finished uploading a trip. This needs to happen because our app will run in the background most of the time.

Android

In Android, we just get an instance of the NoficationManager in onCreate() of our upload service.

  nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

We can then use the nofication manager to add our icon to the try to let the user know that an upload has succeeded.

/**
 * Show a notification that we're done uploading.
 */

 private void showSuccessNotification(String message) {
    // In this sample, we'll use the same text for the ticker and the expanded notification
    CharSequence text = message + getText(R.string.upload_track_success);

    // Set the icon, scrolling text and timestamp
    Notification notification = new Notification(R.drawable.success_128, text, System.currentTimeMillis());
    notification.flags = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
       
    // The PendingIntent to launch our activity if the user selects this notification
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);

    // Set the info for the views that show in the notification panel.
    notification.setLatestEventInfo(this, "SUCCESS", text, contentIntent);

    // Send the notification.
    // We use a string id because it is a unique number.  We use it later to cancel.
    nm.notify(R.string.upload_track_success, notification);
}

iOS

In iOS, notifications work very similarly. Only Apple is allowed to show a title bar icon though. Your local notifications are silently ignored if the app is running in the foreground.

uhh, no code for this one right now because I haven't implemented it yet.  It looks pretty straightforward though from the docs.

WINNER: iOS

Summary

The architecture of Android is just plain BETTER. They don’t restrict what you can do and there always seems to be a good tutorial to follow telling you exactly how to do the exact crazy thing that you want to do. As far as the languages go, I don’t really have a preference for Obj-C vs. Java. They both do the job pretty well. I still don’t quite have my head wrapped around ARC, but that’s a post for another day. I hope you enjoyed this little rant. Flame on people!

Post to Twitter

Posted by Andrew, filed under Android, GPS, iOS, Java, Obj-C. Date: July 5, 2012, 3:46 pm | 6 Comments »

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 »

MileTrack GPS is pain-free business mileage tracking. The gist is that you track your mileage driven using a smartphone or GPS device, upload to the service, and later generate a mileage expense reimbursement report. If you’re interested, we offer a 60-day free trial to test out the system. Head on over to miletrackgps.com to check it out. I’ll also be blogging over there a bit more in the future, so make sure to add that blog to your RSS Feed. We’re on twitter as well @MileTrackGPS

I’ve been working on this project in some way/shape/form for a year and a half now. It started as an electronics project where I was just working on a hardware GPS device. It later morphed into a software project as well once I realized that I couldn’t just sell a piece of hardware alone without competing against really cheap knockoffs manufactured in China.

So, miletrackgps.com was born to solve a pain I had in my life. I wrote a bit about solving this pain with a process over on ourstartupstory.com.

The web front-end is designed of course, with Flex 4.5. The backend is using Java, Spring, GraniteDS, Hibernate, Amazon RDS, all running on the Amazon Elastic Beanstalk. Everything is built with Flex-mojos and maven.

We just released our FREE Android App for tracking mileage to the Google Market. This was developed using Titanium Appcelerator. I blogged a bit about my experience over at riarockstars.com about why I chose that platform for mobile instead of Adobe AIR. Even if you have no real business need for tracking mileage, please give our service a try for the fun of it and send feedback to help us improve the service.

Post to Twitter

Posted by Andrew, filed under Android, Flex, GPS. Date: June 8, 2011, 7:00 am | No 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 | 1 Comment »

Update: We’re in beta! Contact me to get on the beta list: andrew [at] simplifiedlogic.com subject: NitroScreenCap Beta. Also, include your development OS platform.


I’ve been working on a project for Simplified Logic that requires screen capture capabilities. Since this type of feature would be hugely useful to others developing AIR applications, I’ve convinced them to break it out and sell it as a component. This component has two parts. The first is a .swc file you include in your AIR application. The second is a set of native executables that the AIR app will utilize to handle the native part of the screen capture.

So far, I have it working only on Windows. Next on my list will be OSX support followed closely by Linux. Pricing for this component is undetermined at this time.

The code on the AIR side is pretty straightforward for users. You’ll need to use the extendedDesktop AIR profile and package your app as an executable.

protected function screenshotButton_clickHandler(event:MouseEvent):void
{
    nitroScreenCap.takeScreenshot(
        function screenshotResult(bitmapData:BitmapData):void
        {
            screenshotImage.source = bitmapData;
        },
        function screenshotError(error:Error):void
        {
            trace(error.message);
        }
    );
}

Watch the video to get an idea of what it’s capable of.

Unable to display content. Adobe Flash is required.

Post to Twitter

Posted by Andrew, filed under AIR, C++. Date: February 24, 2011, 6:02 pm | 9 Comments »

04  Feb
RIA Rockstars

For those of you who haven’t heard, the Inside RIA blog is no more. Rich Tretola has been kind enough to round up former and new authors to work on a new blog site called RIA RockStars.

My first contribution to the site is Efficient NativeProcess call in Adobe AIR. Enjoy!

Post to Twitter

Posted by Andrew, filed under AIR, C++, encryption. Date: February 4, 2011, 4:09 pm | No Comments »

« Previous Entries