(At the GWT Conference conclusion, someone asked about authoring apps that run on both GWT and Android, I promised I'd detail my findings here...)
One of the purported benefits of Microsoft Volta is "cloud computing" which is the ability to move code execution to different tiers in your application. Now, cloud computing is not really new per se, since mobile code platforms have been able to achieve this for some time with careful architecture, but Microsoft is presenting a vision of doing it painlessly and (relatively) automatically.
I'm of the opinion that it's probably achievable to do it in an automated fashion, but that you are going to hurt user experience. That's because the raw execution performance and I/O bandwidth of the client and server differ dramatically, and simply moving some business logic and data handling code from server to client without thinking about it is going to lead to plenty of pathologically bad cases. It's a case of trying too hard to plug holes in a leaky abstraction.
Choose an abstraction that leaks least
That said, if you are careful, you can do Cloud Computing with GWT and achieve code reuse without suffering too many performance gotchas or pathologically bad cases, but the design work has to be done up front, as it becomes alot harder later to unshackle dependencies and abstraction leaks from you code.
I don't profess to be an expert on this, but here is my experience from porting Chronoscope to several "clouds" (Mobile, JS, Flash, Java2D).
Know your clouds
First, do some up front legwork before you start coding. Hand code a prototype for each cloud to get an intuitive sense of each platform's performance and API support. Then choose a reasonable lowest-common-denominator in terms of API support and performance.
In the case of Chronoscope, I had written JS, Flash, and Java2d prototypes before I wrote the first line of GWT code.
Scaling APIs
Now that you know what your target minimum requirements are, you have two tasks. First, pick an abstraction that represents what's achievable on each of the platforms you're targeting to make sure enough API support is there to either support a feature directly, or emulate it with acceptable performance.
You may have to do several iterations of this to get it right. For example, with Chronoscope, I started out with the Safari/WHATWG Javascript canvas as my abstraction, but I needed text rendering (rotated as well), hit detection, Flash support, and the ability to not have to redraw unchanged portions of the screen.
I started by adding horizontal text rendering by placing DIVs over the Canvas, which of course has several problems (that I addressed later). I then noticed that to make Flash performant I need to ship a whole frame's worth of drawing commands to Flash in a batch. Moreover, Opera's Canvas incrementally updates the display while you're drawing, but they fixed this by adding a non-standard lockCanvasUpdates() function. This led to the addition of OpenGL-style DisplayList capability and a beginFrame()/endFrame() pair of methods.
I initially tried to emulate damage region painting everywhere, but it was too slow. It turns out creating lots of CANVAS elements or MovieClip objects in Flash is not that bad, so I introduced the concept of Layers (ala Photoshop), with a canvas.createLayer() call. The layer system also facilitates adding hit detection in a way which doesn't require alot of scenegraph-style overhead. I'm now confident that this is the right abstraction not only to support slow cloud platforms, but to leverage natively accelerated features of the various platforms.
Decoupling from GWT
Usage of any com.google.gwt.* classes is going to tie your code to running in the GWT cloud. Now, gwt-user is great and we want to use it, but do so in a way that allows it to be swapped out on other platforms.
For Chronoscope, I use the following techniques:
- Stick to JRE Emulation classes as much as possible
- GWT core classes can be reasonably abstracted (Timer, Network requests, etc)
- Abstract away GWT Widgets where possible (MyMenuItem interface vs Menuitem)
- Isolate all JSNI code into a browser specific impl package
- Use a platform specific Factory/Toolkit to create implementation instances of various abstractions (in Chronoscope, this is called the View class)
Package layout
Here's the package layout I use:
- org.timepedia.chronoscope.client - all 'cloud safe' code goes here and in subpackages
- org.timepedia.chronoscope.browser - any class performing any operation that transitively requires gwt-user or JSNI goes here
- org.timepedia.chronoscope.java2d - Java2D cloud specific Canvas implementation
- org.timepedia.chronoscope.client.flash - Flash specific cloud code here
- org.timepedia.chronoscope.android - Android specific View/Canvas stuff here
org.timepedia.chronoscope.server - Servlet Chart Server stuff
Example Abstraction: Timers
Chronoscope does a lot of interpolated animations, and to do so, it needs the use of a timer. Usage of the GWT Timer class would not allow the code to run as an Applet or compile for Android, so instead, this is how timing is done in Chronoscope:
package org.timepedia.chronoscope.client.util;
/**
* Abstraction for running scheduled tasks, independent of JRE environment
*/
public interface PortableTimer {
public void cancelTimer();
public void schedule(int delayMillis);
public void scheduleRepeating(int periodMillis);
double getTime();
}
And of couse, the BrowserView factory class which returns instances that work in GWT:
/**
* Creates a PortableTimer based on GWT's Timer class.
*
* @param run
* @return
*/
public PortableTimer createTimer(final PortableTimerTask run) {
return new BrowserTimer() {
public void run() {
run.run(this);
}
public void cancelTimer() {
cancel();
}
public double getTime() {
return new Date().getTime();
}
};
}
I have created similar abstractions for menus, toolbars, and other things that Timepedia needs, and as I discover new widgets that are needed, I add 'cloud enabled' versions as I go along.
UI Widgets tend to have analogs on every platform that function slightly differently, but it's not hard to seek out minimalist lowest common denominator behavior.
Sometimes this approach fails
So far, I have discussed design techniques to make recompiling for the cloud relatively automatic and painless, but for reasons I cited in the beginning, as well as other fundamental differences, you cannot expect something to be write-once work everywhere. Here's some differences that will bit you:
- JDK1.5 vs JDK1.4 language features (mostly fixed by GWT 1.5)
- JRE Emul collections vs CLDC/J2ME
- Pointing device/events. Not every platform will have gamepad keys, support double-click, single click, drag, etc. The iPhone being the biggest example.
- Network security policies (no access, extremely slow access, same domain access, full access) Sometimes remedied by proxies, but usually a pain in the ass no matter what.
When abstraction fails...
Maintain two branches of your codebase. I do this for J2ME due to CLDC <-> JRE Emul conflicts. I also do it for UI event handling, as each application has to be tailored for the screen format and keyboard/input device of each platform. Years of mobile industry development have taught me the painful fallacy of trying to devise a general purpose app that is not device specific. Want the best user experience? You have to tailor for form-factor and input device.
A preprocessor can sometimes come to the rescue (Foo/*<Bar>*/ -> Foo<Bar> on 1.5+ platforms, Retroweaver can help smooth over 1.5->1.4 collection issues, and there are hacks/tools to inject 1.1 style collection interfaces into J2ME platforms that lack them, such as JDiet)
Future Directions
Both GWT and Android are moving towards compile time declarative UIs, it may be possible in the near future to create a unified declarative UI syntax that allows code generation for both GWT widget layouts and Android.
That's about all I can think of to say on the subject right now. GWT permits Cloud Computing, it was one of the reasons I chose it, and if you're interested in building components that can live within GWT and Android, it is certainly possible.
-Ray
p.s. some might technically quibble and say it's not true cloud computing because all of the tiers I consider are client-side platforms, but Chronoscope does run on the server too, and I can generate old Web 1.0 style image-map interfaces for navigation (JFreeChart can do this as well)