Error: Injector is missing a rule to handle injection into target

david's Avatar

david

24 Sep, 2010 10:36 AM

Hey everybody.

I'm stumbling at a point right now which I thought I already tackled. Take the following scenario. I have a very simple concrete context which maps on mediator to a view and one command to the ContextEvent.STARTUP_COMPLETE event. Here's the code:
`override public function startup():void {

mediatorMap.mapView( Background, BackgroundMediator );
commandMap.mapEvent( ContextEvent.STARTUP_COMPLETE, StartupCommand );
contextView.addChild( new Background() );
super.startup();

}`

When I now try to inject the Background class instance into my StartupCommand instance I get the error mentioned in the subject. My injection looks like this:
[Inject] public var background:BackgroundMediator;

I thought when I call mapView method on the mediatorMap instance I setup the rule for injection. But this seems to be not the case. Both instances - the view and the mediator - exist though. I can retrieve the mediator by this code:
var background:BackgroundMediator = mediatorMap.retrieveMediator( contextView.getChildAt(0) ) as BackgroundMediator;

I tried everything I could imagine which causes the problem. I made sure my ANT tasks keeps the Inject and PostConstruct metadata. I tried using the Flex SDK4 and Flex SDK3. I tried compiling via FDT. I tried using Robotlegs in version 1.3.0 and 1.1.2. The error always occurs.

What am I doing wrong? Is there some delay until the injection works? I actually tried waiting for one second until I called the super.startup() method. This didn't do the trick.

Any other ideas?
David

  1. 1 Posted by Stray on 24 Sep, 2010 10:44 AM

    Stray's Avatar

    Hi David,

    why are you injecting your background mediator into your startup command?

    Normally you'd just create an instance of Background() and when that hits the stage the BackgroundMediator will start to exist. Then your BackgroundMediator instance uses the shared eventDispatcher to communicate both ways with the application.

    I think the best way to help you is for you to explain what you're trying to achieve, because it sounds like your approach is a bit wonky, so something is a bit missing in your understanding of how robotlegs wires up your app.

    We'll get you going.. don't worry!

    Stray

  2. 2 Posted by david on 24 Sep, 2010 11:09 AM

    david's Avatar

    Hi Stray,

    thanks for your quick reply. What I'm trying to achieve is to initialize my view files with data from the actors in my startup command. Looking back at my example it's rather bad. I take that point.

    But imagine I have the following setup. My shell.swf loads an XML file and my application.swf. After loading is finished the shell calls the startup method in the application which then starts Robotlegs. I then create an actor for my XML data and quite a few views. Some views though need some data from the actor in order to initialized. Injecting my actors works just fine, but it always throws this error when I'm trying to inject my mediators into the StartupCommand. To illustrate just the error I boiled down the code to just that part which ended up to be my example above. :)

    Is anything wrong with the approach I described above?

    David

  3. 3 Posted by Stray on 24 Sep, 2010 12:17 PM

    Stray's Avatar

    Hi David,

    yes - as a golden rule the transfer of messages / data in and out of views is handled by the mediators listening for an event.

    So - the flow you describe is totally normal, but the way to achieve it is normally like this: (Yours will be simpler because instead of having to load the XML you are just populating the object, but I thought I'd go for the full flow)

    In your context startup -------------

    injector.mapSingletonOf(IXMLDataLoadingService, XMLDataLoadingService); // you can optionally just mapSingleton if your service doesn't have an interface

    injector.mapValue(String, 'path/to/data.xml', 'xmlDataPath');

    commandMap.mapEvent(ContextEvent.STARTUP_COMPLETE, StartupCommand, ContextEvent);

    ========

    StartupCommand acts upon XMLDataLoadingService, asking it to load data

    the code would be like

    [Inject]
    public var xmlDataLoadingService:IXMLDataLoadingService; // or XMLDataLoadingService if you didn't map it as a singletonOf the interface

    [Inject(name='xmlDataPath')]
    public var xmlDataPath:String;

    public function execute():void
    {
    xmlDataLoadingService.loadXML(xmlDataPath);
    }

    =====

    When Data is loaded, either:

    1) Singleton Models are populated with data (you'd need to have mapped your models as singletons/singletonsOf, and injected them into your XmlDataLoadingService)

    or

    2) Non-persistent VOs are created (if you just need to use the data to configure the view, and after that it's redundant).

    In case 1) The model would then dispatch SomeModelEvent.DATA_READY

    or, in case 2) The service itself would dispatch DataLoadingServiceEvent.SOME_DATA_READY

    (there might be multiple models / vos, which might result in multiple events at this stage)

    These events would be populated with whatever it is that the view is going to need - usually as a strongly typed VO.

    ===

    The mediator for ViewThatNeedsData has the following code:

    public function onRegister():void
    {
    eventMap.mapListener(eventDispatcher, SomeModelEvent.DATA_READY, modelDataReadyHandler); // unless you took approach 2, but it's the same idea
    }

    private function modelDataReadyHandler(e:SomeModelEvent):void
    {
    view.acceptData(e.vo);
    }

    ===

    So - this way, the mediator is totally decoupled from the rest of your classes. Nobody should ever know or care about the poor mediator - it has only one purpose, to act as a link between the view and the app (via events).

    It isn't injectable around the rest of the app (into commands and so on) - don't ever address it directly, just tell it which events it should watch out for, and what view events or signals it should watch out for, and that's all it does. It should NOT have an API. All the functions in a mediator, apart from onRegister / onRemove, should be private.

    In your specific case, it sounds like the XML is being passed through to context.startup() and you're populating a model (Actor) with it. I would inject this singleton model to the StartupCommand and fire what you need from it (a vo usually) on a DATA_READY event that the mediator is listening for.

    Your mediator only exists after your view is added to the stage, so you'd need to make sure that your view is being added before STARTUP_COMPLETE is fired (sounds like you have this covered already).

    Hopefully that helps! Let me know if you still have questions,

    Stray

  4. 4 Posted by david on 24 Sep, 2010 12:53 PM

    david's Avatar

    Hi Stray,

    thank you vey much for your extensive comment. This really cleared up some misconceptions I have about Robotlegs. I'll try your approach right now and see where I'll end up. At the moment it feels like I'll have to write a lot more events than I usually do. But I see the point in leaving out the mediator. In my last project I had to mirror a lot of API methods from my views in my mediators. And this really felt wrong while doing so.

    With your approach I don't see a need anymore to inject a mediator into my StartupCommand. So my error will be "fixed". ;) But what really amazes me is that this error never occurred in my earlier project where injecting mediators into commands is a common practice. Do you know the reason why this approach works, although it should be omitted, when the application is running but leads to an error when applied in the StartupCommand?

    Thanks again for your help.
    David

  5. Support Staff 5 Posted by Shaun Smith on 24 Sep, 2010 03:19 PM

    Shaun Smith's Avatar

    Hi David,

    Back in the old days (pre 1.1.0) this:

    mediatorMap.mapView( Background, BackgroundMediator );
    contextView.addChild( new Background() );
    

    would have left a mapping for the most recently created mediator lying around in the context's injector (see:registerMediator).

    That behavior was a bit sloppy as it could wipe out a user mapped value.

    These days the mediatorMap has its own (child) injector so that it doesn't override any explicit mappings you may have made in the context's injector. This means that you have to manually map a mediator instance into the injector if you want it to be available for general injection. Something like: injector.mapValue(MediatorClass, mediatorInstance).

    Technicalities aside however, there's another reason why it's not a good idea to try to inject mediators into your app:

    mediatorMap.mapView( Background, BackgroundMediator );
    contextView.addChild( new Background() );
    contextView.addChild( new Background() );
    contextView.addChild( new Background() );
    

    Now there are 3 mediators, so the following doesn't make any sense:

    [Inject] public var background:BackgroundMediator;
    

    Hope the clears things up a bit!

  6. 6 Posted by david on 30 Sep, 2010 02:29 PM

    david's Avatar

    Hi Shaun, hi Stray,

    sorry I was busy the last few days finishing a project. I absolutely see the point Shaun pointed out with having multiple instances of one view element. Then it makes totally sense that the mediator is the dumbest player in the game. And with the tips by Stray in his second comment my project worked out well. And in the end I didn't got in any trouble. In addition to that I now see why Signals could be a great accompanying tool to Robotlegs. Though one thing I have a hart time imagining is about sequencing complex application flows, where one thing has to finished when the next step begins. But this way off-topic this post and I must admit I haven't done any research on that so far.

    So, thanks again for your quick and helpful responses.
    David

  7. david closed this discussion on 30 Sep, 2010 02:29 PM.

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