Wednesday, May 30, 2007

Flash.media.Sound & Memory Issues in Flex

Taking a short break from my module woes, I've been working with audio in Flex. In my app I was building a preview intro scan of my listed mp3's. What I noticed is that as the list of mp3's got longer, the memory that my browser was eating up got larger. I haven't really found any articles addressing best practices regarding handling audio in Flex, however I would venture a guess that this has been addressed many times in the past by Flash Developers.

I'm pretty tired so I'll keep this simple. Best way I have found to ensure that an audio stream is destroyed and the memory returned when you stop it:


private var mySound:Sound = new Sound();
private var audio:SoundChannel = new SoundChannel();

private function startPlaying():void{
mySound = new Sound(new URLRequest("music/sample.mp3"));
mySound.addEventListener(Event.COMPLETE, completeSound);
audio = mySound.play();
}

private function stopPlaying():void{
audio.stop();
mySound.removeEventListener(Event.COMPLETE,completeSound);
mySound = null;
}
Trick is to declare the variables outside the function, ensure you configure a sound channel (audio) for your sound, and make sure you that you can reference both from the stop function. Then you need to stop the audio, remove any event listeners, and set sound to null. I found it's also a good idea to do your URLRequest inline, as it seems to get destroyed at the same time as mySound.

Short, simple.

Saturday, May 12, 2007

Can Cairngorm and Modules play nice? Sample App

First I'd like to say thanks to those of you that commented on my previous blog entry. I was inspired to go back and take another look at ApplicationDomain. To be honest, I can't get it to work for me. So I'm just going to put up my prototype application with source code and let you play with it. There is a shell application and 3 very simple modules. The challenge is to have a controller in the shell and still have the modules all work independently of each other. The sample app as it sits on the server works because it's essentially Cairngorm free. Good luck!

Source can be found here. Demo can be viewed here.

Can Cairngorm and Modules play nice?

Let me say, first off, that my wife gave birth to a beautiful baby girl on Wednesday morning, and they are doing just fine. I've been relocated to the spare bedroom so that I can actually function during the day, however I'm missing about 30 hours of sleep this week that I'll never get back. Having said that, I am blogging today because even though I've sort of fixed my latest issues, I'm not entirely sure I've used the best approach.

This is really a sequel to my previous blog... I am still fighting with getting modules to load properly and independently of each other and the shell flex application. Here's the deal...

I have one complex flex module that is built on the Cairngorm 2.2 framework. In itself, the module works fine, and after the progress from the other day, loading multiple instances of the module into a simple shell works fine too. However, when I tried loading multiple instances of the module into the actual application, things died. Why? Because the shell is built on the Cairngorm framework as well. The minute I add in the <services:AppController id="appController"> line to my shell app, subsequent instances of the module don't load properly. I determined that the shell needed to use a different controller than the module, but that wasn't enough (though still necessary). There were funky things happening again when events were triggered - weird casting errors, events firing across multiple module instances, shared models, etc.

A couple of observations:
CairngormEventDispatcher basically returns a singleton. If the shell app gets an instance of this class, then any Modules that load will use the same instance. So, adding the appcontroller to the application will in fact get an instance of the dispatcher. Running through the debugger, I noticed that as I loaded each new instance of the same module, event listeners were added to the singleton's eventDispatcher. So, say your module was listening for the ContactEvent.LOAD_CONTACT event. If you loaded 3 instances of that module, your controller would execute 3 commands, once for each module.

When an event is received by a command, and you try to cast that event back to its origin, i.e. ContactEvent(event), it will only work in the module that actually originated the event. The other modules will throw the following exception:
" cannot convert com.crazedCoders.moduleTest.event::ContactEvent@4b23281 to com.crazedCoders.moduleTest.event.ContactEvent." Trying to test for it, i.e. if(event is ContactEvent) doesn't work since the event is in fact a ContactEvent. Very frustrating.

If you do not use any Caingorm in your shell application, you will not run into these issues, so for some that might be the quick solution. Sadly I was not afforded that luxury, so I had to find another way.

Since I was trying to treat each Module as an autonomous mini-application, I decided that I needed a CairngormEventDispatcher specifically for each Module and the Application. I created a Factory Class that would do just that. Here's the code:

package com.adobe.cairngorm.control{
import mx.modules.ModuleManager;
public class CairngormEventDispatcherFactory
{
private static var instances : Array = new Array;
public static function getDispatcher(obj:Object) : CairngormEventDispatcher
{
// in order for each module developed using cairngorm to be able
// to work independently of the shell and other modules,
// we need to create a hash map of cairngormeventdispatchers
// that are keyed on module instance or application.
// This prevents cross module listening for events,
// which is beneficial especially when loading multiple instances
// of the same module
var ed:Object = ModuleManager.getAssociatedFactory(obj);
var parent:String = "application";
if(ed != null){
trace(ed.name);
parent = ed.name;
}
if(instances[parent] == null){
var cgDispatcher:CairngormEventDispatcher = new CairngormEventDispatcher();
instances[parent] = cgDispatcher;
return cgDispatcher;
}
else{
return instances[parent];
}
}
}
}
It took me a while, but eventually I stumbled upon ModuleManager.getAssociatedFactory(object). This function *seems* to be able to figure out within the context of which Module (if any) the object was instantiated. If an object belongs to the shell, it returns null, if not, then it returns a ton of information, including a name property that contains a unique name i.e. instance77 for the loaded module. I create a new instance of the CairngormEventDispatcher for each instance and stuff it in the static associative array, then I replace the usual CairngormEventDispatcher.getInstance().dispatchEvent(...) with
CairngormEventDispatcherFactory.getDispatcher(this).dispatchEvent(...));
Now the application shell and each module gets it's own singleton, and the overlap seems to be taken care of.

Again, I welcome any feedback. I'm not particularily fond of this solution, and I hope someone out there can offer a better one.

Monday, May 07, 2007

Loading Multiple Independent Instances of a Module

So, it's Monday night... my wife's contractions are 13 minutes apart, and it seems I have nothing better to do than blog. Of course, this is more akin to the calm before the storm that will be the next 24 hours and probably the next six months. Eventually I will once again emerge from the dark precipice that lies ominously before me. But on to modules.

I love Cairngorm... I just upgraded all my projects 2.2 and it was painless, nothing like child labor, I'm told. I am working on a project where we have to build all these pretty much independent modules, and in these modules we stick to Cairngorm as well.

The first problem we ran into occurred when attempting to load 2 instances of the same module. ModuleManager, as it turns out, keys modules by url. So if you try to load the same module twice, you're just getting a reference to the same one. There is no way around this (or so I thought) This has a number of repercussions:

  • Only one ServiceLocator instance can be instantiated - this wonderful little Error message pops up the minute you load a second instance of the module. Quick workaround is to declare the system locator code much like you do model:
    private var dataServices:ServiceLocator = ServiceLocator.getInstance();
    and abstain from using the mxml format:
    <business:Services id="dataServices" />
  • All your module instances will share the same model... so if you say do a search, get a searchresult back and stick that in your model, all your modules will display the same search results, which defeats the purpose of having separate file/image search browsers (for example).

Here's a couple other funky things I tried:

  • Created 2 modules pretty much identical, Mod1.swf and Mod 2.swf. I loaded them up and they both worked fine and independently
  • Created a copy of Mod1.swf and called it Mod1a.swf. I loaded them up and they both worked fine.
  • Tried loading two Mod1.swf and suddenly no independence, plus the ServiceLocator error.

I tried wiring up my own ModuleManager, but after a good couple hours I gave up... I was getting all sorts of weird behaviour. I figured out that everything is based around the url that you pass to ModuleManager or ModuleLoader. I was pretty much ready to give up when I had this really silly notion, I did this:

private var count:int;
private var spaces:String = "";
private function LoadModule():void{
for (var x:int; x < count; x++) spaces += " ";
var info:IModuleInfo = ModuleManager.getModule("mod1.swf" + spaces);
info.addEventListener(ModuleEvent.READY, done);
info.load();
count ++;
}
private function done(event:ModuleEvent):void{
var visual:DisplayObject = event.module.factory.create() as DisplayObject;
tabber.addChild(visual);
}
Much to my surprise, this hack worked. The modules loaded successfully and ran independently. Of course, I probably need to test this some more, but I figured I'd blog it quick before the trauma of childbirth hopefully graces me with some convenient and kind short term memory loss.