The Zukunft

Jonas Huckestein's blog about technology, entrepreneurship and stuff like that

A Wave Gadget + Robot using GWT 2.0 and Google App Engine, Part 1

with 8 comments

[tweetmeme source=”jonashuckestein” only_single=false]I finally got around to playing with Google Wave (wave me: captaincurk@googlewave.com) and decided to build my own extension. Things got somewhat complex, so I made this into a multi-part tutorial. We are going to develop a gadget for Google Wave (in case you don’t have an invite yet, drop me a line) using Google Web Toolkit and wire it up to a robot that lives on the App Engine cloud.

In this part, we are going to develop a simple collaborative gadget that lives on a wave. You should be familiar with Google Wave and have some experience in developing applications using Google Web Toolkit.

Pretext: The Anatomy of a Wave Gadget

Let’s quickly run through how a gadget works with Google Wave.

The original idea of a Google Gadget is to run third-party (javascript) code in a safe environment on a host site. The gadget is provided a confined area in which it can render its content or UI. A gadget is defined by an xml file–the gadget specification–that must be publicly available on the internet. The most popular use for gadgets seems to be iGoogle, Google’s personalized landing page. IIRC original gadgets should run just fine in Wave.

Wave gadgets can additionally make use of the Wave API, which allows the gadget to maintain an internal state and other contextual information that is automatically shared with other participants.

  • A gadget can register callback functions with the API that are called when
    • the wave’s participants change
    • the gadget’s state changes
    • the mode of the wave changes (e.g. from read to edit or playback mode)
  • The state object is a String to String map
  • Running instances of the gadget may submit a change–a “delta”–to the state object that is also a String to String map
  • State updates always include the entire state. It is not known what key in the state map was changed.

Using the same mechanisms that allowed developers to build gadgets using GWT–namely the Google API Libraries for GWT–we can now develop wave gadgets that are aware of their multi-user environment using GWT. The API Libraries thankfully take care of generating the gadget xml specification for us. We can easily access the added API features wave offers (e.g. the callback registrations, the state and the list of participants) using Hilbrand’s Cobogwave Java wrapper.

What are we building?

In this tutorial, we are going to build a gadget that manages a list of messages as follows

  • The UI contains an input field and an “Add” button.
  • Clicking the add button saves the text in the input field and its author to the gadget’s state object
  • Below the button and input we have a table containing all messages and the name and picture of the authors.

So how do we organize our state object? This is one of the most difficult questions for new wave developers. It is important to understand that the only atomic state operations wave supports are the change of a single key. Inspired by Avital Oliver’s  great blog post, I came up with the following scheme for saving gadget state:

  • The key is for each value has the following structure: {type}_{uniqueId}_{property}
  • Nested properties can be saved by appending two keys. For example “family_{id}_child_{cId}_name” might be a good key to save the name of family’s child.

In our example every message would be saved as under two keys: “msg_{id}_message” and “msg_{id}_author”.

Implementation

Before you start, make sure to have set up a GWT + AppEngine project (I use Eclipse and the Google Plugin and called the project WaveListGadgetGWT).

The GWT Module (WaveListGadgetGWT.gwt.xml)

Modifiy your GWT module XML file to inherit Gadgets.gwt.xml and WaveGadget.gwt.xml. Wihtout comments, my WaveListGadgetGWT.gwt.xml looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='wavelistgadgetgwt'>

<inherits name='com.google.gwt.user.User'/>
<inherits name='com.google.gwt.user.theme.standard.Standard'/>

<inherits name="com.google.gwt.gadgets.Gadgets" />
<inherits name='org.cobogw.gwt.waveapi.gadget.WaveGadget' />

<entry-point class='com.onetwopoll.tutorials.client.WaveListGadgetGWT'/>

<source path='client'/>
</module>

The UI Template (ListWidget.ui.xml)

We are going to use GWT 2.0’s brand new UiBinder feature. If you haven’t done this before, enjoy, it’s easy!

Right-click on your src/ folder in eclipse and choose New -> UiBinder from the context menu. Call the widget “ListWidget” and put it into a package of your liking (in my case com.onetwopoll.tutorials.view). Eclipse will set up a .ui.xml and a corrseponding .java file (possibly with sample content for you). Modify your .ui.xml to create the aforementioned minimal user interface like this:

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
	xmlns:g="urn:import:com.google.gwt.user.client.ui">

	<g:HTMLPanel>
		<g:FlowPanel>
			<g:TextBox ui:field="messageBox" />
			<g:Button ui:field="addButton" text="Add" />
		</g:FlowPanel>

		<g:FlexTable ui:field="messagesTable" />
	</g:HTMLPanel>
</ui:UiBinder>

The UI Class (ListWidget.java)

First we’ll set up the class members:

private static ListWidgetUiBinder uiBinder = GWT
		.create(ListWidgetUiBinder.class);
interface ListWidgetUiBinder extends UiBinder {
}

@UiField
protected Button addButton;

@UiField
protected FlexTable messagesTable;

@UiField
protected TextBox messageBox;

protected WaveFeature wave;

The UiFields will be bound to the UI template to create the user interface and the WaveFeature is required because ListWidget.java is going to contain all our logic. This manual dependency injection is not necessarily best-practice, but it reduces the amount of code for this tutorial.

Next we need to write the constructor, which sets up the UI and most importantly registers a StateUpdateEventHandler (from cobogw’s wrappers) with the wave:

	public ListWidget(WaveFeature wave) {

		this.wave = wave;

		initWidget(uiBinder.createAndBindUi(this));

		// refill the table when the state changes
		wave.addStateUpdateEventHandler(new org.cobogw.gwt.waveapi.gadget.client.StateUpdateEventHandler() {
			@Override
			public void onUpdate(StateUpdateEvent event) {
				refillTable(event.getState());
			}
		});
	}

The actual logic is implemented in the refillTable function and addButton’s clickHandler. Let’s start with the latter; all we need to do is build a HashMap with the keys we want to update and submit it to the gadget’s state. Wave will take care of creating stateUpdateEvent’s in all running gadget instances:

	@UiHandler("addButton")
	void onClick(ClickEvent e) {
		if(!messageBox.getText().isEmpty()) {

			HashMap<String, String> delta = new HashMap<String,String>();

			// generate a unique id
			String id = Long.toString(new Date().getTime());

			// add the two state keys
			delta.put("msg_"+id+"_message", messageBox.getText());
			delta.put("msg_"+id+"_author", wave.getViewer().getId());

			// push the change to wave
			// note how we don't update the UI just yet
			// because this will immediately trigger a state update event
			wave.getState().submitDelta(delta);
		}
	}

}

Note that we do not need to update the UI now. An update event will be fired even in the gadget instance that has submitted the change.

Let’s look at the refillTable function. It takes a state as argument and then render’s the state’s contents in the messagesTable. Obviously deleting the entire table and refilling it is not very efficient, but it illustrates how each state update event contains the entire state and not only changed keys.

	protected void refillTable(State state) {

		messagesTable.removeAllRows();

		Map<String, Integer> rowMap = new HashMap<String, Integer>();

		// iterate over all keys in the state
		for(int i=0; i<state.getKeys().length(); i++) {

			String[] split = state.getKeys().get(i).split("_");

			String id = split[1];
			String field = split[2];
			String value = state.get(state.getKeys().get(i));

			int row = -1;

			// since every message is saved in two state keys,
			// we need to find out if we have already seen this one
			if(rowMap.containsKey(id)) row = rowMap.get(id);
			else {
				row = messagesTable.getRowCount();
				rowMap.put(id, row);
			}

			if(field.equals("message")) {
				messagesTable.setWidget(row, 0, new Label(value));
			} else {
				FlowPanel authorPanel = new FlowPanel();
				Participant author = wave.getParticipantById(value);
				if(author != null) {
					authorPanel.add(new Image(author.getThumbnailUrl()));
					authorPanel.add(new Label(author.getDisplayName()));
				}
				messagesTable.setWidget(row, 1, authorPanel);
			}
		}
	}

This really is the most minimal logic I could think of that recognizes multiple users and has a orderly state structure. Feel free to play around with the logic; for instance, try adding a ParticipantUpdateEventHandler to the wave object which deletes all a user’s messages when he leaves the wave. You could also add a delete button behind all messages as soon as the user switches to edit mode.

The Entry Point Class (WaveListGadgetGWT.java)

Now we’ll fix up the entry point class. Most importantly (and despite the entry-point declaration in the module file), our class does no longer implement EntryPoint. Instead, it extends WaveGadget<UserPreferences> and is annotated with @ModulePrefs. the ModulePrefs are used when generating the gadget’s XML spec.

WaveGadget provides the WaveFeature object that we used above and subclasses must implement init(UserPreferences), which is called when the gadget is ready. All we need to do now is attach a new ListWidget to our RootPanel in init and we’re done! Ignore the UserPreferences for now. My entire class looks like this:

package com.thezukunft.tutorials.client;

import org.cobogw.gwt.waveapi.gadget.client.WaveGadget;
import com.google.gwt.gadgets.client.UserPreferences;
import com.google.gwt.gadgets.client.Gadget.ModulePrefs;
import com.google.gwt.user.client.ui.RootPanel;

@ModulePrefs(title = "WaveListGadgetGWT",
		author="Jonas Huckestein",
		author_email="jonas.huckestein@me.com",
		height=400)
public class WaveListGadgetGWT extends WaveGadget<UserPreferences> {
	@Override
	protected void init(UserPreferences preferences) {
		RootPanel.get().add(new ListWidget(getWave()));
	}
}

Note that at the time init() is called, the state and participants of the wave are not initialized. They are only initialized when their corresponding events are fired.

Run!

Let’s get this thing running in a wave. Here’s what you need to do:

  • Compile your gadget using the Google plugin for eclipse. In case you are uploading it to the App Engine, this will be done automatically in the next step.
  • Upload the contents of your build directory (in my case, by default, war/wavelistgadgetgwt/) to some public place in the internet. I used App Engine for this. Just click the App Engine icon in Eclipse and make sure your project is set up to use an appengine application you own. I deployed the gadget to the application identifierwavelistgadgetgwt.
  • Locate your gadget XML specification on the internet. In my case this was in http://wavelistgadgetgwt.appspot.com/wavelistgadgetgwt/com.thezukunft.tutorials.client.WaveListGadgetGWT.gadget.xml.
  • Add the Gadget to a wave! Press the “Add Gadget by URL” icon in Wave and paste your gadget XML specification URL.

Voila, you should now see the UI. If everything went well, it should look like the following image. If it didn’t, check out the links on debugging at the bottom of this post or drop me a line in the comments.

The unstyled gadget

Try adding some messages and using the playback mechanism. This is great.

Done! (for now)

You can download the source code and all required libraries of this tutorial as Eclipse project from here. I went ahead and added some styles and something called a dynamic height feature so that the height automatically adjusts with the content of the gadget. The end result looks like this:

The finished gadget

You can add the widget to your own wave by using the following gadget spec URL: http://pretty.latest.wavelistgadgetgwt.appspot.com/wavelistgadgetgwt/com.thezukunft.tutorials.client.WaveListGadgetGWT.gadget.xml

Okay, let’s recap:

  • We wrote our Gadget using GWT and the cobogwave Wave API for GWT.
  • We compiled the Gadget to a .gadget.xml file
  • We deployed the .gadget.xml file to the AppEngine (or anywhere else)
  • We added the Gadget to a Wave and it worked

Some things we did not discuss today, most notably how to locally test your gadget, so stay tuned for what’s coming next

  • How to test and debug your Gadgets locally (spoiler: by implementing mocks for all Wave API classes) and how to remove clutter from the development process.
  • How to build a Wave robot that integrates with our gadget
  • How to deploy a finished extension

In the meantime, enjoy these other reads:

Written by Jonas Huckestein

2010/02/08 at 10:54 am

Posted in technology

Tagged with , , , , , , , ,

8 Responses

Subscribe to comments with RSS.

  1. […] This post was mentioned on Twitter by Erik Reisig, Jonas Huckestein. Jonas Huckestein said: A Wave Gadget Robot using GWT 2.0 and Google App Engine, Part 1 http://wp.me/pKZ6O-m #google #wave #gwt […]

  2. Social comments and analytics for this post…

    This post was mentioned on Twitter by jonashuckestein: A Wave Gadget Robot using GWT 2.0 and Google App Engine, Part 1 http://wp.me/pKZ6O-m #google #wave #gwt…

  3. Very good until here I’m undestading all!!!!!

    What about the next part?

    hiperion

    2010/02/10 at 3:45 am

  4. Have you got an error like this ?

    Invoking Linker Google Gadget
    [ERROR] No gadget manifest found in ArtifactSet.

    Deployed the project is ok this error is i host mode.

    hiperion

    2010/02/10 at 7:47 am

    • Hi,

      thanks for following along!

      It is currently not possible to test Google Gadgets in hosted mode at all. This is because of the way the Google Gadgets API works.

      I have built my own solution-the WaveConnector-that decouples the actual gadget implementation from the Gadget API and provides mock Wave API methods thus allowing you to test your gadget in hosted mode (with some restrictions).

      I am expecting to publish that today :) Stay tuned!

      Part 2 of the tutorial will be on adding a robot and it will be available either by the end of this week or the beginning of next week.

      Cheers,

      Jonas

      Jonas Huckestein

      2010/02/10 at 11:50 am

  5. […] site has everything you need to get started! It already contains the little project I made for the tutorial last week, so you can just downloaded the waveconnector-gwt-turnkey archive and start from there. There is no […]

  6. Hello,

    Nice work! I just want to know how you can browse the app directory after uploading it to Google app Engine. I mean how can I locate my gadget xml file after uploading to Google app Engine.

    Thanks.

    Kayode Odeyemi

    2010/02/12 at 10:44 am


Leave a comment