# Event Queue subscribed more than once

asked 2012-02-22 04:08:21 +0800

gellaps
157 1

i have an iframe and using eventqueue to publish an event,
but the event fired more than once,
when i trace, the iframe publish one time, but the main page subscribe three times,

here's my sample code

Iframe Composer

	EventQueue qe = EventQueues.lookup("general",EventQueues.SESSION, true);

public void onUpload$tbImport(UploadEvent event){ // read excel Media media = event.getMedia(); File dest = new File(Executions.getCurrent().getDesktop().getWebApp().getRealPath("/")+"WebContent\\excel\\"+media.getName()); try { Files.copy(dest, media.getStreamData()); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } ExcelImporter importer = new ExcelImporter(); importer.setInputFile(Executions.getCurrent().getDesktop().getWebApp().getRealPath("/")+"WebContent\\excel\\"+media.getName(),sessionFactory); try { importer.importBranch(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } //publish event qe.publish(new Event("onImportBranch",null,"tes")); }  Main Composer[/i  private EventQueue qe= EventQueues.lookup("general",EventQueues.SESSION,true); public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); qe.subscribe(new EventListener() { @Override public void onEvent(Event event) throws Exception { System.out.println("subscribe event"); System.out.println("event name "+event.getName()); } }); }  and the output console print 3times subscribe event event name onImportBranch subscribe event event name onImportBranch subscribe event event name onImportBranch  did i do something wrong? please guide me, i'm using Iframe, and using Session scope in Eventqueue, i've got NPE when i use Group scope thanks in advance Glen delete retag edit ## 8 Replies Sort by » oldest newest answered 2012-02-22 07:06:26 +0800 gekkio 899 1 http://gekkio.fi/blog You are subscribing to an event queue with a long life-time, but you are never unsubscribing from it. It is important to know that event listeners are never automatically unsubscribed from an event queue and will remain there unless you manually unsubscribe or the event queue itself is disposed. doAfterCompose is called every time your page is rendered, so the event queue will contain one listener per every page refresh. For example, try hitting F5 to reload the page and you'll see that the amount of subscribers will increase. This is effectively a memory leak since the old subscribers will retain the whole component trees even though the old desktops are dead. The solution is to store the event listener object somewhere and unsubscribe from the queue once it is no longer needed. There are two main ways to do this: Personally I think the best and safest way is to use both of these methods. If you cannot use method 1, don't worry because method 2 itself is sufficient. 1. Unsubscribe immediately If you have a application-specific situation where you know that the event listener can be unsubscribed, use this. It is also possible (although very tricky) to unsubscribe when the composer's component is destroyed (for example user reloaded the page). The basic idea is this:  private EventQueue qe = EventQueues.lookup("general",EventQueues.SESSION,true); private EventListener qeListener = new EventListener() { @Override public void onEvent(Event event) throws Exception { System.out.println("subscribe event"); System.out.println("event name "+event.getName()); } }; public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); qe.subscribe(qeListener); } public void onClick$someButton() {
// This could be a button click, or any other application-specific situation when you know that the listener should be unsubscribed
qe.unsubscribe(qeListener);
}


2. Unsubscribe lazily
The basic idea is to check if the desktop is still alive in the event listener. If not, unsubscribe.

  private EventQueue qe = EventQueues.lookup("general",EventQueues.SESSION,true);

public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);

qe.subscribe(new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
if (!desktop.isAlive()) {
qe.unsubscribe(this);
return;
}
System.out.println("subscribe event");
System.out.println("event name "+event.getName());
}
});
}


An alternative is checking if the composer's component has a page. If not, the component has been detached.
if (self.getPage() == null) {
qe.unsubscribe(this);
return;
}


answered 2012-02-22 09:46:45 +0800

gellaps
157 1

hi gekkio, that's very nice explanation,

i've tried your suggestion one by one, but still fired more than once,
i tried to unsubscribe directly after finish do something, but still the same

My Code

  private EventQueue qe = EventQueues.lookup("general",EventQueues.SESSION,true);

public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);

qe.subscribe(new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
System.out.println("subscribe event");
System.out.println("event name "+event.getName());
qe.unsubscribe(this);
}
});
}


before using unsubscribe the event fired 3 times,
when using unsubscribe, it fired 2 times.

how about unsubscribe lazily?, the event fired faster than the created condition,
any suggestion?

answered 2012-02-22 11:05:15 +0800

gekkio
899 1
http://gekkio.fi/blog

You could try the lazy unsubscribe method with both of the checks I mentioned:

public void onEvent(Event event) throws Exception {
if (!desktop.isAlive() || self.getPage() == null) {
qe.unsubscribe(this);
return;
}
// ...
}


If after that you still get more than one event, the only explanation is that there are indeed two active desktops (= browser windows) in the session.
Even if you only have one browser window open, this is completely normal because ZK cannot 100% accurately know whether you actually have a window open or not. ZK has mechanisms that try to inform the server that a window has closed but it can never be completely accurate.

So, from the point of view of the code you cannot distinquish between "really open" browser windows and old browser windows.
In that case, it's safe to just ignore this issue and do whatever you intend to do in the event listener. The lazy unsubscribe mechanism above will clean up the event listeners when it's possible.

answered 2012-10-22 14:10:27 +0800

rickcr
704 7

How do you handle this in ViewModels, using an MVVM architecture, that are Subscribing to a queue with the @Subscribe annotation? Also, I don't see this mentioned anywhere in the docs on the queue section here http://books.zkoss.org/wiki/ZK%20Developer's%20Reference/Event%20Handling/Event%20Queues This seems like important stuff but I don't see it documented?

Currently I'm subscribing to queues in my View Models and SelectorComposers like so:

@Subscribe("myQueue")
public void doSomething(Event evt) {
//how would I unsubscribe in here if the Desktop isn't alive?


Should we start wiring (@Wire) the ui components in our view model in order to make these types of checks?

I posted here http://www.zkoss.org/forum/listComment/20848-How-to-avoid-multiple-desktops-being-created about a situation where I was seeing my composer's listener fired more than once and I figured the main issue was that I was creating a page instance multiple times by calling Executions. createComponents each time a menu item was clicked. I fixed that by following what was posted at the end of this forum post by paowang http://www.zkoss.org/forum/listComment/20779-Very-difficult-to-find-a-simple-example-of-this-common-layout-with-MVVM

answered 2012-10-25 15:54:31 +0800

rickcr
704 7

Anyone?

per above question related to : "How do you handle this in ViewModels, using an MVVM architecture, that are Subscribing to a queue with the @Subscribe annotation? Also, I don't see this mentioned anywhere in the docs on the queue section here http://books.zkoss.org/wiki/ZK%20Developer's%20Reference/Event%20Handling/Event%20Queues This seems like important stuff but I don't see it documented?"

answered 2012-11-06 19:25:13 +0800

rickcr
704 7

bump

answered 2012-11-14 17:17:00 +0800

rickcr
704 7

No one seems to be replying to this, but I now ran into this again.

This seems like a very big weakness in ZK. In fact it's actually easier for me to just use a ViewModel for EVERYTHING, since I could post global commands to a view model and don't have to worry about cleaning up listeners.

I was tying to be somewhat "pure" in the sense that I didn't want my view models to know about things in the UI, so for example, in a control that needs to create components when navigation items are clicked on, I was doing that in a SelectorComposer, but now I need to trigger a page opening up by notifying that controller - yet how do I do that notification from a view model without having that controller listen on a queue? And since it would have to listen on a queue that means refreshing the page would have issues. So instead I'm going to have to use a ViewModel?

I really wish I could get the best practice here documented by someone from the ZK team.

answered 2015-07-21 10:23:30 +0800

WilliamB
1609 1 6

Not sure how to have access to desktop and self as i'm using MVVVM and facing the same issue.

[hide preview]

## Stats

Asked: 2012-02-22 04:08:21 +0800

Seen: 570 times

Last updated: Jul 21 '15

## Related questions

Support Options
• Email Support
• Training
• Consulting
• Outsourcing