Search code examples
actionscript-3flashair

AS3 Air Desktop - Dispatch Custom Event from SWF in ApplicationStorageDirectory


I have a series of Air Desktop games that I created. Before the game can be played, the user must first log in. In order to streamline things, I created the login system in a separate project and saved it as a swf file called, "dashboard.swf". When the game is opened, it loads dashboard.swf and displays the login screen. In addition to the login functionality, dashboard.swf also handles a bunch of other stuff like common settings amongst the games.

I didn't want to recompile each game every time I made a change to the dashboard.swf. So, I have it download from a server. I was originally downloading, saving, and loading dashboard.swf in the ApplicationDirectory and it worked fine on Macs. After testing on Window 10 and doing some research, I found that ApplicationDirectory for non-OSX machines is read-only.

So, I changed the location of the dashboard.swf to the ApplicationStorageDirectory. When I run it on my Mac, the swf loads just fine, but the first custom event that gets dispatched throws and error:

TypeError: Error #1034: Type Coercion failed: cannot convert com.thisapp.event::CustomEvent@12786a9fed31 to com.thisapp.event.CustomEvent

Both CustomEvent.as files are identical. It fires just fine when dashboard.swf is saved to and loaded from the ApplicationDirectory on the Mac. Once I move it to ApplicationStorageDirectory, I get this error. So I know it's not an issue with the actual custom dispatcher. Bubbling is true and so in Cancellable.

What would be causing the Type Coercion failure in this situation?

Here's my custom dispatcher:

public class CustomEvent extends Event {

    public static const GOT_RESULT: String = "gotResult";
    public var result: Object;

    public function CustomEvent(type: String, result: Object = null, bubbles: Boolean = false, cancelable: Boolean = false) {
        // constructor code
        super(type, bubbles, cancelable);
        this.result = result;
    }

    public override function clone(): Event {
        return new CustomEvent(type, result, bubbles, cancelable);
    }

}

From my dashboard.swf:

dispatchEvent(new CustomEvent(CustomEvent.GOT_RESULT, null,true,true));

In my Main class for the desktop app:

var dashboardURL:String = File.applicationStorageDirectory.url +"dashboard.swf";

  var myContext:LoaderContext = new LoaderContext(); 
myContext.applicationDomain = ApplicationDomain.currentDomain; 
var urlReq:URLRequest = new URLRequest(dashboardURL); 
var ldr:Loader = new Loader();
ldr.load(urlReq, myContext); 
ldr.contentLoaderInfo.addEventListener(Event.COMPLETE,loadit);
    function loadit(e:Event){
dashboard = e.target.content as MovieClip;
addChild(dashboard);
dashboard.addEventListener(CustomEvent.GOT_RESULT, runLogin);
}

ANSWER See @BadFeelingAboutThis's answer below to understand why the 1034 error is happening. Here's how I fixed it:

First - Download the swf from the server (I'm using the GreenSock's LoaderMax):

private function dowloadDashboard(){
    var url:String  = "https://path/to/your/swf/on/the/server.swf";
    var queue:LoaderMax = new LoaderMax({name:"mainQueue",onComplete:completeHandler});
    //Note: the format is set to "binary"
    queue.append( new DataLoader(url, {name:"mySwf",format:"binary", estimatedBytes:3000}) );
    queue.load();

    function completeHandler(event:LoaderEvent):void {
    //Note: "mySwf" is the name I gave to the DataLoader above.
         var b:ByteArray = LoaderMax.getContent("mySwf");
    //loadDashboard() is the next function and I'm passing the ByteArray to it.
        loadDashboard(b);
    }
                }

Next - Load the swf with the proper context using the ByteArray:

private function loadDashboard(b:ByteArray) {
var myContext:LoaderContext = new LoaderContext();
        myContext.allowLoadBytesCodeExecution = true;
        myContext.allowCodeImport = true;
        myContext.applicationDomain = ApplicationDomain.currentDomain; 
var ldr:Loader = new Loader();
ldr.loadBytes(b,myContext);
ldr.contentLoaderInfo.addEventListener(Event.COMPLETE,loadDone);
}

Last - Add your swf to the stage:

        function loadit(e:Event){
            dashboard = e.target.content as MovieClip;
            addChild(dashboard);
}

I hope that helps somebody! Remember, in my situation, I have a Desktop Air app that is downloading and loading a swf file that lives on a server.


Solution

  • This error typically means that you have the same Class coming into your root application from different sources.

    In your case CustomEvent class must exist in both the host SWF file as well as your loaded in SWF file.

    Because your loaded SWF is not in the same application domain as the host SWF, flash/AIR will not see overlapping classes as the same class. So you loaded in SWF CustomEvent is now seen as com.thisapp.event::CustomEvent@12786a9fed31 which (though exactly the same) is seen as a totally different class than com.thisapp.event:CustomEvent. Since your code references the latter, anytime a CustomEvent from the host tries to get stuffed in an object reference typed :CustomEvent it will throw a coercion error because they aren't actually same class.

    Usually, the remedy for this problem is to specify the context for the loaded SWF so it integrates the loaded Classes into it's own domain. This should overwrite the host CustomEvent with the loaded SWF's CustomEvent

    var ldr:Loader = new Loader();
    
    //The second parameter for load takes a LoaderContext
    ldr.load(new URLRequest(File.applicationStorageDirectory.url +"dashboard.swf"), new LoaderContext(false, ApplicationDomain.currentDomain)); 
    ldr.contentLoaderInfo.addEventListener(Event.COMPLETE,loadit);
    
    function loadit(e:Event){
        dashboard = e.target.content as MovieClip;
        addChild(dashboard);
        dashboard.addEventListener(CustomEvent.GOT_RESULT, runLogin);
    }
    

    Read more about the loader context here and here