My application uses gin and a hand-made MVP framework based loosely
off of gwt-presenter. Some adaptation may be necessary to fit your
particular frameworks, but here goes:
public class ProxyPresenter<T extends Presenter> implements Presenter
{
private static class ProxyView implements View {
SimplePanel proxy = new SimplePanel();
ProxyView() {
}
@Override
public Widget asWidget() {
return this.proxy;
}
protected void setView(View view) {
this.proxy.setWidget(view.asWidget());
}
}
private boolean asyncCalled;
private boolean bound;
private HandlerManager bus;
private T impl;
private Provider<T> provider;
private Queue<Command> queue;
private ProxyView view;
public ProxyPresenter(HandlerManager bus, Provider<T> provider) {
this(bus, provider, false);
}
public ProxyPresenter(HandlerManager bus, Provider<T> provider,
boolean eager) {
this.bus = bus;
this.provider = provider;
this.queue = new LinkedList<Command>();
this.view = new ProxyView();
if (eager) {
ensurePresenter();
}
}
@Override
public void bind() {
this.bound = true;
queue(new Command() {
@Override
public void execute() {
ProxyPresenter.this.impl.bind();
}
});
}
@Override
public View getView() {
return this.view;
}
@Override
public void handleHistory(final HistoryItem item) {
queue(new Command() {
@Override
public void execute() {
ProxyPresenter.this.impl.handleHistory(item);
}
});
}
@Override
public boolean isBound() {
return this.bound;
}
@Override
public void release() {
queue(new Command() {
@Override
public void execute() {
ProxyPresenter.this.impl.release();
}
});
this.bound = false;
}
protected void ensurePresenter() {
if (!this.asyncCalled) {
this.asyncCalled = true;
GWT.runAsync(new RunAsyncCallback() {
@Override
public void onFailure(Throwable reason) {
ProxyPresenter.this.bus
.fireEvent(new ApplicationExceptionEvent
(reason));
}
@Override
public void onSuccess() {
// get impl instance
ProxyPresenter.this.impl =
ProxyPresenter.this.provider
.get();
// fill-in proxy view
ProxyPresenter.this.view.setView
(ProxyPresenter.this.impl
.getView());
// execute any queued commands
while (ProxyPresenter.this.queue.peek() != null) {
Command cmd = ProxyPresenter.this.queue.poll
();
cmd.execute();
}
}
});
}
}
protected void queue(Command command) {
ensurePresenter();
if (this.impl != null) {
command.execute();
} else {
this.queue.offer(command);
}
}
T getPresenter() {
return this.impl;
}
}
Then, in my gin module, instead of using an explicit bind, I use a
@Provides method, like so:
@Provides
Presenter getRealPresenter(HandlerManager bus,
Provider<RealPresenter> provider) {
return new ProxyPresenter<RealPresenter>(bus, provider);
}
The rest is automagic!
--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To post to this group, send email to google-we...@googlegroups.com.
To unsubscribe from this group, send email to google-web-tool...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-web-toolkit?hl=en.
In fact, no it does not work properly with multiple instances, and
this is a problem I discovered shortly after originally posting this.
The solution I devised was to make the Proxy abstract and simply
create concrete sub-classes for each Presenter I want to proxy
(anonymous sub-classes do not work!). This is a little tedious, but
the abstraction I wrote makes it pretty simple to implement. Below is
the updated code. Note that I also changed the ViewProxy to use a
LayoutPanel instead of a SimplePanel so that proxied views that
implement RequiresLayout are supported.
/**
* PresenterProxy wraps a Presenter implementation and acts as a
gateway to that
* Presenter. The wrapped Presenter is created on-demand the first
time any
* method is called, or optionally it can be created eagerly at the
time the
* PresenterProxy is created.
*
* Instantiation of the wrapped Presenter occurs behind a
GWT.runAsync() call so
* that complex wrapped Presenters may be split out into code
fragments during
* module compilation.
*
* In order to achieve this "delayed" instantiation, the
PresenterProxy must be
* given a Provider<T extends Presenter> instance to provide the
actual
* implementation at the appropriate time.
*
* Additionally, a ViewProxy is utilized that consists of a
LayoutPanel to
* minimize the impact on the UI. The ViewProxy can be inserted into
the DOM
* immediately without waiting for the wrapped Presenter to become
available.
*
* Any calls made to the wrapped Presenter before the instance becomes
available
* are queued in a buffer and later replayed when the instance becomes
* available. Once available, all calls are executed immediately on
the wrapped
* instance.
*
* @author jcarlson
*
* @param <T>
*/
public abstract class PresenterProxy<T extends Presenter> implements
Presenter {
private static class ProxyView implements View {
LayoutPanel proxy = new LayoutPanel();
ProxyView() {
}
@Override
public Widget asWidget() {
return this.proxy;
}
protected void setView(View view) {
this.proxy.clear();
this.proxy.add(view.asWidget());
}
}
private boolean asyncCalled;
private boolean bound;
private HandlerManager bus;
private T impl;
private Queue<Command> queue;
private ProxyView view;
public PresenterProxy(HandlerManager bus) {
this(bus, false);
}
public PresenterProxy(HandlerManager bus, boolean eager) {
this.bus = bus;
this.queue = new LinkedList<Command>();
this.view = new ProxyView();
if (eager) {
ensurePresenter();
}
}
@Override
public final void bind() {
this.bound = true;
queue(new Command() {
@Override
public void execute() {
PresenterProxy.this.impl.bind();
}
});
}
@Override
public final View getView() {
return this.view;
}
@Override
public final void handleHistory(final HistoryItem item) {
queue(new Command() {
@Override
public void execute() {
PresenterProxy.this.impl.handleHistory(item);
}
});
}
@Override
public final boolean isBound() {
return this.bound;
}
@Override
public final void release() {
queue(new Command() {
@Override
public void execute() {
PresenterProxy.this.impl.release();
}
});
this.bound = false;
}
protected final void onAsyncFailure(Throwable reason) {
PresenterProxy.this.bus
.fireEvent(new ApplicationExceptionEvent(reason));
}
protected final void onAsyncSuccess(T impl) {
// set impl instance
this.impl = impl;
// fill-in proxy view
this.view.setView(PresenterProxy.this.impl.getView());
// execute any queued commands
while (ResizingPresenterProxy.this.queue.peek() != null) {
Command cmd = PresenterProxy.this.queue.poll();
cmd.execute();
}
}
/**
* The key method that subclasses must override.
* This allows each GWT.runAsync() call to be in its own
* concrete class, thus allowing the compiler to produce
* multiple exclusive fragments.
*/
protected abstract void runAsync();
void ensurePresenter() {
if (!this.asyncCalled) {
this.asyncCalled = true;
runAsync();
}
}
void queue(Command command) {
ensurePresenter();
if (this.impl != null) {
command.execute();
} else {
this.queue.offer(command);
}
}
}
And an implementation of the proxy:
public class MyPresenterProxy extends
PresenterProxy<ProfilePresenter> {
private Provider<ProfilePresenter> provider;
@Inject
public MyPresenterProxy(HandlerManager bus,
Provider<ProfilePresenter> provider) {
super(bus);
this.provider = provider;
}
@Override
protected void runAsync() {
GWT.runAsync(new RunAsyncCallback() {
@Override
public void onFailure(Throwable reason) {
onAsyncFailure(reason);
}
@Override
public void onSuccess() {
MyPresenter presenter = MyPresenterProxy.this.provider
.get();
onAsyncSuccess(presenter);
}
});
}
}
> > google-web-tool...@googlegroups.com<google-web-toolkit%2Bunsubs cr...@googlegroups.com>
To unsubscribe from this group, send email to google-web-tool...@googlegroups.com.
I had tried a few different scenarios, but I found that unless the
actual call to GWT.runAsync for each proxied Presenter was in a unique
class then the compiler wouldn't split out the code properly. I'm
guessing the code-splitting mechanism in the compiler pivots at least
in part on the class containing the call.
> > > > queue(new...
>
> read more »