-
FEATURED COMPONENTS
First time here? Check out the FAQ!
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
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()); } }); }
if (self.getPage() == null) { qe.unsubscribe(this); return; }
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?
thanks in advance
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.
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
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?"
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.
Asked: 2012-02-22 04:08:21 +0800
Seen: 574 times
Last updated: Jul 21 '15