Singelton in Modular Application
I'm having a bit of an issue with modular application, I've made a scaled down version of it to post on here. The issue is that I cannot access my singleton classes in the child modules. The example I've made has two SWFs Application (parent) and Module (child).
This is the NavMediator in the Application (parent), the
DataModelProxy is a getter and setter on the "current" string
variable.
public class NavMediator extends ModuleMediator
{
[Inject]
public var view:NavView;
[Inject]
public var proxy:DataModelProxy;
[Inject]
public var injector:IInjector;
private var _loader:Loader;
public var page:IModule;
override public function onRegister():void
{
eventMap.mapListener(view.home, MouseEvent.CLICK, onClick);
eventMap.mapListener(view.contact, MouseEvent.CLICK, onClick);
eventMap.mapListener(view.gallery, MouseEvent.CLICK, onClick);
}
private function onClick(event:MouseEvent):void
{
switch(event.target)
{
case view.home:
proxy.current = "Home";
break;
case view.contact:
proxy.current = "Contact";
break;
case view.gallery:
proxy.current = "Gallery";
break;
}
_loader = new Loader();
var context:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
var request:URLRequest = new URLRequest("Module.swf");
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError);
_loader.load(request, context);
}
private function onComplete(event:Event):void
{
if(page != null)
{
view.removeChild(DisplayObject(page));
page.dispose();
page = null;
}
page = event.currentTarget.content;
page.parentInjector = injector;
view.addChild(DisplayObject(page));
_loader.removeEventListener(Event.COMPLETE, onComplete);
_loader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
_loader = null;
}
private function onError(event:IOErrorEvent):void
{
trace("FILE ERROR:", event.target, event.type);
}
All the classes below are the Module
public class Modular extends Sprite implements IModule
{
private var _context:IModuleContext;
public function Modular()
{
super();
}
public function set parentInjector(value:IInjector):void
{
_context = new ModularPageContext(this,value);
}
public function dispose():void
{
if(parent && parent.contains(this))
parent.removeChild(this);
_context.dispose();
_context = null;
}
}
public class ModularPageContext extends ModuleContext
{
public function ModularPageContext(contextView:DisplayObjectContainer, parentInjector:IInjector = null)
{
super(contextView, true, parentInjector);
}
override public function startup():void
{
injector.mapSingleton(DataModelProxy);
mediatorMap.mapView(ModularView, ModularMediator);
contextView.addChild(new ModularView());
}
}
public class ModularMediator extends ModuleMediator
{
[Inject]
public var view:ModularView;
[Inject]
public var proxy:DataModelProxy;
override public function onRegister():void
{
view.title = proxy.current;
}
}
Sorry for the long post but figured it was easiest way.
Comments are currently closed for this discussion. You can start a new one.
Keyboard shortcuts
Generic
? | Show this help |
---|---|
ESC | Blurs the current field |
Comment Form
r | Focus the comment reply box |
---|---|
^ + ↩ | Submit the comment |
You can use Command ⌘
instead of Control ^
on Mac
1 Posted by filipelima on 26 Jan, 2011 05:39 PM
Im currently having the same issue... need a way to access a class which is mapped in the injector, though from a class not part of my mappings.
...well, a not too hacky way to it that is.
2 Posted by digitaldavenyc on 27 Jan, 2011 04:03 PM
is it just not possible to share singleton classes between modularized SWFs?
3 Posted by Stray on 27 Jan, 2011 04:44 PM
Parent injector mappings are picked up by the child swf's injector, so while sibling-to-sibling injection isn't provided for 'automagically', shell-to-child mappings are.
If you want to share a singleton between sibling swfs and it's created in one of them, here's how I would go about it:
1) Have the module context that 'owns' the singleton map it normal - eg:
injector.mapSingletonOf(SomeInterface, SomeImplementation);
2) Dispatch a custom event on the module shared event dispatcher, passing the instance as the payload.
var instanceToShare:SomeInterface = injector.getInstance(SomeInterface);
var instanceEvent:SomeInterfaceSupplyEvent = new SomeInterfaceSupplyEvent(INSTANCE_SUPPLIED, instanceToShare);
dispatchToModules(instanceEvent);
3) In the modules that want to use this instance, wire a command on the ModuleCommandMap to this event.
moduleCommandMap.mapEvent(SomeInterfaceSupplyEvent.INSTANCE_SUPPLIED, ReceiveSomeInterfaceInstance);
4) Pick up the event, and map the payload to the correct interface in the recipient module using
instanceReceived:SomeInterface = instanceEvent.instance;
injector.mapValue(SomeInterface, instanceReceived);
Always do these mappings to interfaces and not concrete types to avoid class collisions.
You may wish to suspend the recipient module from starting until any of this has happened - you can change the event you wire your bootstrap commands to.
You'll need to be mindful of race conditions. This needs to happen in the order [1 and 3] then [2]. [4] will be triggered by [2] provided [3] has happened first.
For that reason I would consider making it a request-response event so there's an INSTANCE_REQUESTED that is sent by the module that wants access to the singleton, and then step 2 is in a Command which is bound to this event. In [1] you'd add something like:
moduleCommandMap.mapEvent(SomeInterfaceSupplyEvent.INSTANCE_REQUESTED, SupplySomeInterfaceInstance);
The code for [2] would sit in the SupplySomeInterfaceInstance Command.
If that's not clear let me know.
**An alternative approach:
Unfortunately sibling-to-sibling mappings are very hard to implement automagically because mapping from the child to the parent has the potential to overwrite all sorts of stuff.
There's no direct access AFAIK from the module context to the parent Injector.
However, you could potentially expose the parent injector from the shell using a method on the shell API, and then you could pass the injection to the shell to fulfil. There are a number of reasons why I prefer not to do it that way, and personally I think the request-response pattern is easier to follow and less likely to cause the kinds of problems that end up taking five hours to work out, but you might prefer a less verbose approach.
I like to keep my modules ignorant of the shell (actually, they're ignorant of the fact that they're modules too), but that's just a preference really.
Hope that helps,
Stray
4 Posted by Stray on 27 Jan, 2011 04:53 PM
Hmm...
It looks like get and set parentInjector are available in SwiftSuspenders, but not in the IInjector - let me chase that up a little further...
Support Staff 5 Posted by Till Schneidere... on 27 Jan, 2011 05:12 PM
You can use the parentInjector by either casting to Injector, or by
adding a mapping in the startup sequence
injector.mapValue(Injector, injector);
and directly injecting as the concrete type:
[Inject] public var ssInjector : Injector; // has to have a different
name so as not to collide with the base class's "injector" var
6 Posted by Stray on 27 Jan, 2011 06:09 PM
Excellent! Thanks for that Till.
For clarification - set / get parentInjector isn't included in the robotlegs IInjector because for a long time we've been trying to ensure that the actual injector was interchangeable, so you could switch out the swift-suspenders injector for a different one.
However, this might not be a policy we continue with. So watch this space for RL 1.5 complete with access to the parentInjector.
In the meantime, any of the 3 approaches suggested will work.
7 Posted by digitaldavenyc on 27 Jan, 2011 09:15 PM
Stray thanks for the detailed post. I'm not sure I understand Till's approach, how would you get access to the proxy via that way?
8 Posted by Stray on 27 Jan, 2011 09:41 PM
Here's a little more expansion on what Till was saying:
In theory you can access the parent Injector (which the child injectors in the modules look to for mappings if they don't have their own) via the injector in your module.
However - for various reasons - the api functions for set and getParentInjector on the Injector aren't included in the robotlegs IInjector interface. So you would need to cast your injector to be a swift suspenders Injector (which it is, but your injector is typed to the robotlegs IInjector interface):
var ssInjector:Injector = Injector(injector)
then you could access the parent Injector:
var parentInjector:Injector = ssInjector.getParentInjector();
and now you can make your mapping in a way that all the other modules can share it too
parentInjector.mapSingletonOf(IThingClass, ThingClass);
You still need to be careful of race conditions, but provided you make sure that the other modules don't request this mapping (ie you don't create any objects that have this singleton injected) until after this code has run, you should find that the singleton is available in all the modules.
I've found that it's useful to have every module make its mappings and then declare itself 'ready'. A utility I use tracks all those "ready" events and when every module is "ready" then it dispatches an "Ok! Everybody's ready! Let's go!" event and then the modules are bootstrapped into actually starting from there. I use the same utility on shutdown - so every module has done any clean up stuff before my app actually quits. (It's an AIR app).
Happy to share that code if it's useful to you (you might prefer to roll your own).
Let me know if that's still not clear,
Stray
9 Posted by digitaldavenyc on 27 Jan, 2011 09:56 PM
Ah OK now I see what Till was saying that does make sense. Yeah I'd love to see an example of that code, I'm going to be writing a lot of modular apps for the next few months. Thanks so much for the help.
Support Staff 10 Posted by Till Schneidere... on 28 Jan, 2011 01:47 PM
Now that Stray has already explained what I meant (but didn't explain
myself, sorry for that), let just quickly add that a future version of
SwiftSuspenders will support live injections that get updated
automatically when the mapped value changes. With that ability, the
required boiler-plate for enabling the described scenario should
hopefully get much smaller.
11 Posted by digitaldavenyc on 28 Jan, 2011 04:10 PM
that sounds like an amazing new feature for swift suspenders, I can't wait for it!
Support Staff 12 Posted by Till Schneidere... on 28 Jan, 2011 04:14 PM
Thanks! Unfortunately, you'll have to wait at least some time: I don't
think I'll be able to release 2.0 before late Spring or so.
But I'll at least publish a roadmap over the next few days. Not that
that would help you in any way at all ...
Stray closed this discussion on 18 Feb, 2011 06:57 PM.