These days I started working with Java Desktop, Swing, JTables and similar components. I have done a lot of things using Java, but they were either console apps or web apps - so it was already time I started working with Desktop applications.
There are some problems with the model that they use to build the dialogs and applications, some of the components have lots of deprecated methods, the dialog class is not responsible for its events (you have to define it yourself), but the worst thing I noticed is that exceptions that are not catched in methods that are called in response to events are silently ignored, completely swallowed. During some research I found this page that describes a way to add an uncaught exception handler, but it's some sort of workaround.
So, when I work with dialogs, I try to make the dialogs just part of the flow of the application, but in a way that each dialog is responsible for collecting some user data or outputting some information, but never being responsible for complex business logic or by important application flow.
For example:
public static void main(String[] args) {
try {
final LoginDialog loginDlg = new LoginDialog();
final LoginCredentials loginCredentials = loginDlg.getLoginCredentials(); // Displays the login dialog and will return only when the user presses the "ok" button or close the dialog
if (loginCredentials == null) {
return; // The user cancelled the login - we should just leave the app
}
// Depending on how the login is performed some sort of user message has to be displayed here
final LoginToken loginToken;
try {
loginToken = performLogin(loginCredentials);
}
catch(CannotLoginException ex) {
GUIUtils.messageBox("Cannot perform login (" + ex.getMessage() + ")");
return; // More friendly would be to try again
}
// We are authenticated. Let's collect user data
final UserDataEntryDialog dataEntryDialog = new UserDataEntryDialog();
final UserData userData = dataEntryDialog.collectUserData();
// Do something with the user data
final SomeBusinessLogic someBusinessLogic = new SomeBusinessLogic();
// It would be nice to display some sort of progress information - that can either be achieved through the last dialog displayed, or through some sort of progress window
someBusinessLogic.doYourStuff(userData);
catch(Exception unexpected) {
GUIUtils.displayErrorMessage("Unexpected error (sorry buddy)", unexpected);
loggerInstance.error("Unexpected error", unexpected);
}
}
The "progress window" is rather important, that can either be obtained by using a "global progress window" (it's the easiest approach), or by defining an interface that will be implemented by all dialogs:
public interface ProgressDisplayDialog {
void display(String msg);
}
(the user should always know that something is happening and that the application hasn't simply died)
The important thing about the example above is the fact that there's no dialog chaining. Each dialog only knows about itself and is responsible for retrieving some sort of data that will be used by the business class or the main method, or whoever needs it. It's some crude form of MVC. The main advantage of this approach resides on the fact that dialogs do not invoke business methods. So, if in the action method of some dialog there's an exception that was not catched (or catched and ignored, something not so hard to do on UI classes as they tend to have a lot of boilerplate code), then the damage will be restricted to that dialog only - probably some event will not be completed - something like changing the selected index of a drop down list or coloring a cell in a table. In UI classes, the right thing to do in event methods is to catch the exceptions and tell the user about it.
It also makes the navigation on the application very clear and it makes it easy to replace blocks of code if you need to.
Also about exceptions, in my current company we use this API where almost every method on every class throws 3 or 4 checked exceptions, which is a pain in the butt. When I am working with this API, I make all my private methods throw an Exception (so whatever happens will be propagated to the caller), and I make my public methods either throw Exceptions, RuntimeExceptions, or some Exception class that is related to the business purpose of the class that contains the method being executed. At least by making your private methods throw Exception, your free yourself from a lot of boilerplate code. Alternativelly, if you're not too lazy or if you use some smart IDE, you can add 5 or 6 exceptions to the method signature and let other deals with that.
I don't like checked exceptions :-).
This is my blog in English, about the stuff I do for a living, and about the stuff I read about the stuff I do for a living - when I am not working.
Friday, May 29, 2009
Subscribe to:
Posts (Atom)