tag:robotlegs.tenderapp.com,2009-10-18:/discussions/problems/237-lifetime-of-services-and-commands-or-weird-service-behaviorRobotlegs: Discussion 2018-10-18T16:35:21Ztag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-04T19:23:17Z2011-01-04T19:23:17ZLifetime of Services and Commands, or weird Service behavior<div><p>Hello,</p>
<p>I don't have great experience with robotlegs, but I think that
the problem that you described occurs because you are mapping the
service as a singleton. In this case when you inject your service
you will access the same instance of that class. Or in other words
you are adding the same listener several times, every time when the
command is executed. So the question is how did you map your
service into the context.</p>
<p>By the way, you can use hasEventListener instead of
removeEventListener.</p></div>krasimirtag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-04T19:30:12Z2011-01-04T19:30:12ZLifetime of Services and Commands, or weird Service behavior<div><p>Yes, I'm mapping the service as a Singleton:</p>
<pre>
<code> injector.mapSingletonOf(ICdnService,OoyalaService);</code>
</pre>
<p>(btw, what does the Of do? I've just copied examples using it
and it works)</p>
<p>I'm assuming that both the service and especially its
eventDispatcher stick around for a while, which is why i try to
remove existing event listeners first.</p>
<p>A thought just occurred to me: if the command itself is short
lived, then adding an event listener from two instances of the
command might result in two separate listeners on the
eventDispatcher, which might result in the event being dispatched
twice and both caught by the current command instance.</p>
<p>i am going to try removing the event listener in the event
handler, when the command instance is still the same. maybe using
weak references would help too. i'll report back, thanks!</p>
<p>g</p></div>Gerry Kohtag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-04T19:37:26Z2011-01-04T19:37:26ZLifetime of Services and Commands, or weird Service behavior<div><p>that was totally it! i don't know the details of how
eventDispatchers work, but i'm guessing that having two separate
command instances added to its listeners array somehow caused it to
fire two events on the same bus, which were received by the current
command twice. removing the event listener in the event handler
solved the problem, and i suspect weak references would too, but
that might depend of garbage collection timing.</p></div>Gerry Kohtag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-04T19:38:11Z2011-01-04T19:38:11ZLifetime of Services and Commands, or weird Service behavior<div><p>here is the fixed code:</p>
<pre>
<code> // Public Interface
//
[Inject]
public var event:PersistVideoEvent;
[Inject]
public var service:ICdnService;
[Inject]
public var model:ComposerModel;
// Overrides
//
override public function execute():void
{
var video:VideoAsset = model.currentVideo;
eventDispatcher.addEventListener(VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED,onVideoDetailsUpdated);
service.updateVideoDetails(video.id,video.startDate,video.endDate,video.name,video.descriptions,video.actions);
}
// Event Handlers
//
protected function onVideoDetailsUpdated(event:VideoDetailsUpdatedEvent):void
{
eventDispatcher.removeEventListener(VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED,onVideoDetailsUpdated);
if (event.video.id == model.currentVideo.id)
{
// success
Alert.show("Video has been successfully updated","Success",Alert.OK);
}
else
{
// fail
Alert.show("Video was not successfully updated","Fail",Alert.OK);
logger.error("Attempt to update video details failed");
}
}</code>
</pre></div>Gerry Kohtag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-04T19:41:50Z2011-01-04T19:41:50ZLifetime of Services and Commands, or weird Service behavior<div><p>Can you please give more details about your service class. About
ICdnService and OoyalaService. Are they extending something.<br>
If you ask me, I'll add the listener for
<code>VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED</code> in the
body of the Service, not outside it. I don't know the structure of
your application but for me it looks much much better if you place
the listeners there and connect them with private functions.</p>
<p>From the documentation:<br>
mapSingleton provides a single instance of the requested class for
every injection. Providing a single instance of a class across all
injections ensures that you maintain a consistent state and
don’t create unnecessary instances of the injected class.
This is a managed single instance, enforced by the framework, and
not a Singleton enforced within the class itself.</p>
<p>mapSingletonOf is much like mapSingleton in functionality. It is
useful for mapping abstract classes and interfaces, where
mapSingleton is for mapping concrete class implementations.</p></div>krasimirtag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-04T19:47:30Z2011-01-04T19:47:30ZLifetime of Services and Commands, or weird Service behavior<div><p>The "Of" in mapSingletonOf() differentiates between a singleton
that is<br>
mapped as an interface implementation versus a singleton that
isn't<br>
mapped to an interface.</p>
<p>The difference is in what data type you use for your [Inject]
properties<br>
-- if you want to use the concrete data type, use mapSingleton(),
if you want to specify an interface use mapSingletonOf().</p>
<p>(For services I would always use an interface, because it makes
it easy to swap them out for testing or if the implementation
changes -- both of<br>
which I've personally encountered.)</p>
<p>Back to your general question, I think the crux of the issue is
that<br>
Commands aren't meant to have state, including doing event
handling.<br>
(There are several interesting variations on Commands that do
handle events, but that's not the "standard" MVCS
implementation.)</p>
<p>In a typical MVCS implementation, the command calls the service
and<br>
that's the end of the command. The service has its own event
handlers<br>
for its own events. When the service result returns, it dispatches
an<br>
event indicating success or failure, including the relevent
data.</p>
<p>That way, any actions that should be triggered by the
result/failure of<br>
the service can be mapped to the event that the service dispatches.
For<br>
example, if you want to update a model based on the service call,
you<br>
can have a command mapped to the event that the service dispatches
and<br>
update the model from there. If you want to display some data or
change<br>
the view, you can have a mediator listen for that event and notify
the<br>
view. (Or the mediator can listen for a change event dispatched by
the<br>
model and use that to trigger the view change.)</p>
<p>I realize the Alert in the event handlers is probably just for
testing,<br>
but I would personally try to stay away from that since Alert is
clearly<br>
a view-related thing.</p>
<p>Paul</p></div>Paul Robertsontag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-04T20:07:53Z2011-01-04T20:07:53ZLifetime of Services and Commands, or weird Service behavior<div><p>the service implements Actor, and ICdnService is just its
interface. i use interfaces for services so that must be why i've
always used mapSingletonOf. I don't need any listeners inside the
service except for HTTPService listeners, and i don't want to pass
those events on to the application for coupling reasons. I prefer
to use events rather than callbacks to interface the service to the
rest of the application, but am always open to better ways to do
things!</p>
<p>here is the service call code. it is basically 4 or 5 REST calls
chained together, with the final result handler dispatching the
success event:</p>
<pre>
<code> public function updateVideoDetails
(
embedCode:String,
startTime:Date=null,
endTime:Date=null,
name:String=null,
descriptions:ArrayCollection=null,
actions:ArrayCollection=null
):void
{
sendRequest(OoyalaServiceConstants.EDIT_HOST,parameters,retrieveExistingActions);
function retrieveExistingActions(event:ResultEvent):void
{
if (event.statusCode == 200)
{
sendRequest(OoyalaServiceConstants.QUERY_HOST,parameters,removeExistingActions);
}
else
{
throw new Error("Attempt to update video attributes failed");
}
}
function removeExistingActions(event:ResultEvent):void
{
if (event.statusCode == 200)
{
sendRequest(OoyalaServiceConstants.SET_METADATA_HOST,parameters,deletionResultHandler);
}
else
{
throw new Error("Attempt to retrieve video actions failed");
}
function deletionResultHandler(event:ResultEvent):void
{
if (event.statusCode == 200)
{
addActions();
}
else
{
throw new Error("Attempt to remove existing actions failed");
}
}
}
function addActions():void
{
sendRequest(OoyalaServiceConstants.SET_METADATA_HOST,parameters,resultHandler);
}
function resultHandler(event:ResultEvent):void
{
if (event.statusCode == 200)
{
dispatch(new VideoDetailsUpdatedEvent(video));
}
else
{
throw new Error("Attempt to add actions to video failed");
}
}
}</code>
</pre></div>Gerry Kohtag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-04T20:11:14Z2011-01-04T20:11:14ZLifetime of Services and Commands, or weird Service behavior<div><p>Paul,</p>
<p>I see your point about the lifespan of commands. I'm open to
listening for service events directly in the appropriate mediator
rather than the command itself. it seemed to me that given the
choice to couple a command to a service or a view mediator to a
service that the command would be the better choice, but i suppose
a service interface change will require a change somewhere in the
app anyway.</p>
<p>thanks!</p></div>Gerry Kohtag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-04T21:09:40Z2011-01-04T21:09:40ZLifetime of Services and Commands, or weird Service behavior<div><p>Like you I don't usually like to dispatch service-specific
events into<br>
my app. Typically I do one of two things with my services:</p>
<ol>
<li>
<p>Create a custom event containing the result data, fault info,
etc.<br>
and dispatch an instance of that from the service. That way I'm
not<br>
tying my app to the specific event used in the service unless I
really<br>
like it for some reason.</p>
</li>
<li>
<p>Inject my model into the service, and use the service result to
set a<br>
value on the model directly. Setting the value on the model
then<br>
dispatches a change event that is picked up by any interested parts
of<br>
the code such as mediators. This couples my service to my model, so
some<br>
people (myself included at times) would discourage this. This is
where<br>
you get into judgment calls about "appropriate" coupling.</p>
</li>
</ol>
<p>Paul</p></div>Paul Robertsontag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-04T22:39:26Z2011-01-04T22:39:26ZLifetime of Services and Commands, or weird Service behavior<div><p>Switch to AS3Signals! It makes your code much more elegant.</p></div>Abel de Beertag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-05T00:34:27Z2011-01-05T00:34:27ZLifetime of Services and Commands, or weird Service behavior<div><p>Abel, I have been trying to switch to Signals :) I downloaded
the core project and another one that provided a SignalContext or
something, got errors, and got no responses to my posts on
here.</p>
<p>You could help me greatly by pointing me to the repositories of
the projects needed to add signals support into my application, and
please make a note of which versions work for you, when I had
played with it the head revisions in the master branch did not work
for me.</p></div>Gerry Kohtag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-05T00:34:59Z2011-01-05T00:34:59ZLifetime of Services and Commands, or weird Service behavior<div><p>thanks paul,</p>
<p>those are good options.</p></div>Gerry Kohtag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-06T13:16:48Z2011-01-06T13:16:48ZLifetime of Services and Commands, or weird Service behavior<div><p>I fail to see the point of this since the command will be
discarded</p>
<p>// Event Handlers</p>
<pre>
<code>//
protected function onVideoDetailsUpdated(event:VideoDetailsUpdatedEvent):void
{
eventDispatcher.removeEventListener(VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED,onVideoDetailsUpdated);</code>
</pre></div>Nikos tag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-06T17:26:23Z2011-01-06T17:26:23ZLifetime of Services and Commands, or weird Service behavior<div><p>nikos, when you execute a command, insert a breakpoint and
inspect eventDispatcher. it has an array of listeners. since
commands are short lived, each command has a different object id
and occupies a different spot in the listeners array.</p>
<p>i suspect that by not removing the listener in the same command
instance that created it results in extra entries in
eventDispatcher.listeners.</p>
<p>i don't know enough about the specifics or the robotlegs event
system to know why that resulted in my event handler being called
once for each extra entry in the listeners array, but when i moved
the removeEventListener call to the same command instance that
registered it, the problem went away.</p></div>Gerry Kohtag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-06T17:35:11Z2011-01-06T17:35:11ZLifetime of Services and Commands, or weird Service behavior<div><p>see this thread, maybe you have extra mediators?<br>
<a href=
"http://knowledge.robotlegs.org/discussions/questions/134-how-do-i-keep-my-view-from-getting-extra-mediators-every-time-it-is-added-to-the-stage">
http://knowledge.robotlegs.org/discussions/questions/134-how-do-i-k...</a></p></div>Nikos tag:robotlegs.tenderapp.com,2009-10-18:Comment/45597222011-01-06T19:20:03Z2011-01-06T19:20:52ZLifetime of Services and Commands, or weird Service behavior<div><p>@Gerry: I use the following versions of the core libraries /
extensions in my latest Signals enabled project:</p>
<p><a href=
"https://github.com/robotlegs/robotlegs-framework">Robotlegs
v1.4.0</a> / <a href=
"https://github.com/robertpenner/as3-signals">AS3Signals v0.8</a> /
<a href=
"https://github.com/joelhooks/signals-extensions-CommandSignal/">Joel
Hooks' Signals extension for Robotlegs</a></p>
<p>You should add the Robotlegs and AS3Signals SWC files to your
classpath and add the files inside the extension's 'src' folder to
your own 'src' folder. To be able to access the signalCommandMap,
your Context should extend SignalContext. Let me know if you can
get it to work.</p></div>Abel de Beer