Large MVP applications and GWT.runAsync()

342 views
Skip to first unread message

jarrod

unread,
Jan 25, 2010, 11:16:23 PM1/25/10
to Google Web Toolkit
While building an application for my company, I needed a way to make
large sections of the application sit behind a split point. After
organizing my application into "modules" of related functionality, I
came up with slick, easy way to make those "modules" split out
automatically: by using a proxy presenter.

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!

Joe Cheng

unread,
Feb 8, 2010, 3:31:11 AM2/8/10
to google-we...@googlegroups.com, jarrod....@gmail.com
Jarrod, are you using this ProxyPresenter more than once within the same application (with different presenter type parameters), and found that it introduced the split points as expected? I originally tried something like this but found that it would only ever introduce a single split point, no matter how many times I used it.

I was able to solve the problem using deferred binding and was about to write a blog post about it, but if your code actually works as desired then I need to go back and figure out what I was doing wrong.


--
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.


jarrod

unread,
Feb 9, 2010, 11:42:35 AM2/9/10
to Google Web Toolkit
Joe,

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>

Joe Cheng

unread,
Feb 9, 2010, 4:14:57 PM2/9/10
to google-we...@googlegroups.com
OK, great, I went through a similar approach previously. The new approach I'm using is similar but you can remove the boilerplate in the proxy implementation, as the deferred binding mechanism will fill it all in. In fact since you're using Gin it will literally look like this:

public abstract class MyAsyncShim extends AsyncShim<MyPresenter> { }

I'll reply to this thread when I have something written up. I would also like to adapt your idea of using a command queue, right now I'm doing something similar but not guaranteeing the methods will be executed in the order they were invoked.

To unsubscribe from this group, send email to google-web-tool...@googlegroups.com.

jarrod

unread,
Feb 9, 2010, 7:32:56 PM2/9/10
to Google Web Toolkit
Ah, well I'd be interested to see what you come up with as far as
reducing the boilerplate.

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 »

Reply all
Reply to author
Forward
0 new messages