Get on your ViewModel

I’ve been quietly hacking away at the Eclipse Business Expense Reporting Tool (EBERT) from the Examples project for some time. My goal is to turn this application into a shining example of how you can go about making a real (albeit relatively simple) application based on Eclipse RCP, eRCP, and RAP.

It’s taken me an embarrassing while to sort out coordination of the views. The application is composed of three views. In the RCP and RAP versions, the views are composed as shown below with the BinderView on the left, the ExpenseReportView on the top-right and the LineItemView on the bottom-right. In the eRCP version, the views are stacked and selectively exposed.

At first, I used the selection service to coordinate the views. The BinderView’s ListViewer is, for example, configured to be a selection provider and the ExpenseReportView is configured to be a selection consumer: when an ExpenseReport is selected, the ExpenseReportView updates itself. One nice thing about this mechanism is that it allows the views to be totally decoupled from one another and further allows other views to act as selection providers. Another very nice thing about using the selection service for this is that it works well in the multiple-user RAP environment since each user gets their own copy of the workbench (and, by extension, their own copy of the selection service). On the downside managing things like the case when, for example, a selected ExpenseReport is removed requires some work (when an ExpenseReport is deleted from the BinderView, you should expect the ExpenseReportView and LineItemView to become empty). It’s not particularly difficult to manage this, but it feels a little cludgy. That, and the selection service isn’t actually implemented in eRCP (because, it’s really not useful in that environment).

I opted instead to introduce the ViewModel class. ViewModel provides, as the name suggests, a model of my view. For this application, the view model is pretty simple: it keeps track of the “current” ExpensesBinder, ExpenseReport, and LineItem. The various views all tell the view model about the user’s activities. The view model is updated in response to those activities, and notifies the various views about the change.

When, for example, an ExpenseReport is selected in the BinderView, the view model is informed of the change:

// TODO Consider using addPostSelectionChangedListener instead
expenseReportViewer.addSelectionChangedListener(new ISelectionChangedListener() {
	public void selectionChanged(SelectionChangedEvent event) {
		IStructuredSelection selection = (IStructuredSelection)event.getSelection();
		getViewModel().setReport((ExpenseReport) selection.getFirstElement());
	}
});

The view model responds by setting the “current” ExpenseReport is set to the newly selected one and the “current” LineItem is to null. Then all the listeners are informed of the change. The code is a pretty straightforward implementation of the observer pattern:

public void setReport(ExpenseReport report) {
	this.report = report;
	this.lineItem = null;

	Object[] listeners = listenerList.getListeners();
	for(int index=0;index<listeners.length;index++) {
		IViewModelListener listener = (IViewModelListener) listeners[index];
		listener.reportChanged(this.report);
		listener.lineItemChanged(this.lineItem);
	}
}

Each of the views listen to the view model. When the observer notifies them of the change, they react appropriately. The LineItemView, for example, responds to the lineItemChanged(LineItem) method by updating itself to reflect the change:

IViewModelListener viewModelListener = new IViewModelListener() {
	public void binderChanged(ExpensesBinder binder) {
	}

	public void lineItemChanged(final LineItem item) {
		syncExec(new Runnable() {
			public void run() {
				setLineItem(item);
			};
		});
	}
	public void reportChanged(ExpenseReport report) {}
};

The other types and views are handled in a similar way.

The big challenge with this approach is, of course, the multiple-user environment of RAP. To manage this, I needed to come up with a notion of user state. The view model is just part of the user state.

I’ll talk about this next time…

Please poke holes in this if there’s something that can be done better.

This entry was posted in Examples. Bookmark the permalink.

5 Responses to Get on your ViewModel

  1. Jacek Pospychala says:

    Hm isn’t it actually a viewer responsibility to know, which elements it shows as selected?
    I can imagine extender wanting to show several reports at the same time (e.g. in custom editors) – he’d have difficulty using ViewModel. I’d also look at implementing custom IStructuredSelection.
    Not sure why SelectionService would be not useful in eRCP.

  2. Christian says:

    Have you considered using JFace databinding to synchronize your widget state with your ViewModel state? In particular, I think the Master-Detail capability would be helpful. Also, if you can successfully encapsulate your presentation logic in the ViewModel, you will realize benefits in testability of the presentation logic without having to mock/test double the UI widgets. This is sometimes referred to as “subcutaneous testing” because the testing occurs “just beneath the skin” of the application. Subcutaneous testing allows you to test the most important part of the presentation without having to deal with brittle, widget-level testing.

  3. Wayne Beaton says:

    I swear that I commented on your comments on Friday, but my comment seems to have vanished. Anyway…

    @Jacek… no. It’s not the viewer’s responsibility. It’s the model’s. Of course, there are different types of models. My application has a domain model which doesn’t know anything about UI in general or selection in particular. The view model does. The view is a reflection of the view model. This is classic MVC stuff.

    My view model doesn’t restrict the ability to create an editor which works for a particular instance. It merely provides the subcutaneous layer (like that term) which defines how my UI operates.

    There’s nothing stopping my view model from participating with the workbench selection. We could set up a listener which informs my view model of the changes in the workbench selection and all the views which are hanging off the model would update accordingly.

    The selection service is not supported on eRCP because (a) eRCP only shows one view at a time, so (b) you really don’t need it, and (c) not implementing it saves some small amount of precious memory space on a small device.

    @Christian… I like the term “subcutaneous testing” and intend to use it in regular conversation as much as possible. I did consider using JFace Databinding, but it’s not supported on eRCP.

  4. Jacek Pospychala says:

    ok, having views model is ok, works nicely e.g. in GEF. however EBERT domain model seems to be a simple tree:
    ExpansesBinder contains ExpanseReports contains LineItems, similar to JDT’s
    Project contains Packages contains Classes.
    So knowing which LineItem is selected you know it’s parent ExpanseReport and it’s parent ExpansesBinder. Unless you want to be able to select one ExpanseReport and edit LineItem of other (which may be confusing to user).

    From comments, eRCP seems to be pretty limited (no databinding, no selection service) – so the real trick must be to have both portable UI (RCP/RAP/eRCP) and take the best from all the three platforms, rather than minimum common denominator.

  5. Wayne Beaton says:

    @Jacek, the model manages the on-the-glass relationships. When the user selects an ExpenseReport in the BinderView, the ExpenseReportView updates to reflect the new selection. At the same time, the LineItemView also updates to show no selection (for the time-being, this is the desired behaviour). I could do this with the selection service: my ExpenseReportView could listen for the selection of ExpenseReports and update accordingly; my LineItemView could also listen for ExpenseReport selections and blank itself out when a new one is selected. What do I do when the selected ExpenseReport is deleted and there is no selection? If my LineItemView listens for no selection, how do I know that the type of thing that’s not selected is an ExpenseReport? I could consider the class of the view that provides the selection, but that would introduce a tight coupling.

    FWIW, my object model currently doesn’t have any notions of parent–a LineItem doesn’t know its ExpenseReport. This could be easily changed (and might be)

    My view model models the user interface at a relatively high level, managing the interoperation of multiple views. While it is indeed true that this example is relatively simple and that we probably could get away without the view model, one of the motivations for this example is to provide something we can point to as an example of a view model when the question comes up on the newsgroups.

    Indeed, eRCP is limited. You can add Java 1.4/CDC-.1.1/Foundation-1.1 to the list. If you look at the example, you’ll see that I’ve tried to make a core of lowest-common-denominator bundles, but have extended the base behaviour with platform-specific enhancements, like cell editors in the table on RCP, login/multiple user on RAP, and Command support on eRCP.

    I intend to spend time over the next few months documenting as much of this as possible.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s