Quick Start: Screen Navigation
Overview
This chapter will present ways to handle user navigation from one screen to another, such as when the user successfully passes the login screen. You will also be introduced to some additional design techniques that should improve the reusability of your form components and the maintainability of your applications.
There is no single "right way" to develop Echo applications, so you will need to evaluate if this approach is appropriate for your projects.
This chapter presents some advanced topics targeted toward developers of medium and large applications. If you are new to event-driven architectures such as Echo or Swing, you may want to skip this chapter until you have a more experience upon which to evaluate the applicability of the ideas discussed below.
Navigation Logic Architectures
The primary impediment to the reuse of form components (within an application or among several applications) is the tight coupling between screens due to navigation. The following sections will introduce you UI navigation logic architectures and discuss how we can control the user's navigation between screens in a clean manner outside of our screen processing logic.
Coupling Coupling is an object-oriented term that roughly means "dependency". For more information, review the suggested readings at the end of this chapter.
Model 1 Architecture
Controlling navigation between screens looks simple on its surface. When a user performs an action that causes a new screen to be displayed, you can simply set the contents of the parent Window to a new instance of the next screen, like we did in the last chapter:
public void attemptLogin(ActionEvent evt) {
... [process username and password] ...
if (validLogin) {
Window win = (Window) getParent();
win.setContent(new PortalScreen());
} else {
...
}
}
This approach to navigation control is called a Model 1 architecture. It is analogous to hard-coding URLs between pages in a JSP application. Unfortunately, experience has demonstrated that this approach is brittle and hard to maintain when used in medium and large applications. It is also difficult to manage when the the navigation logic is dynamic in response to:
- How the user got to the current screen.
- The user's input on the current screen.
- The environment external to the screen, such as the time of day or completion of an asynchronous process.
In fact, when considered from the perspective of object-oriented design (OOD), embedding the navigation logic in a screen does not make sense. Using OOD principles, we can consider a screen as a collection of UI widgets and the logic to marshal and unmarshal information between those widgets and the middle-tier, business logic, or enterprise services. An application is a collection of screens and the navigation logic between those screens. This conceptualization of the roles of these classes nicely encapsulates the responsibilities of each and provides a clear demarcation line between the concerns of screens and their applications. From this, we see that a screen should not "link" the user to the next screen -- that is the responsibility of the application.
Model 2 Architecture
The problems encountered with Model 1 architectures have motivated developers to find ways of removing navigation logic from the screens of their applications. These efforts have given rise to Model 2 architectures such as the venerable Struts framework. In a Model 2 architecture, screens inform a central controller of the results of their processing and the controller directs the application to the next screen, as appropriate.
In Echo, this is done by making form components fire Events when they have completed their functions and making the EchoInstance listen for those events and handle navigation.
Applying Model 2 to the LoginScreen
To migrate our example application to a Model 2 architecture, our LoginScreen will fire an ActionEvent when the user has successfully authenticated herself. When the EchoInstance instantiates the LoginScreen, the EchoInstance will add itself as an ActionListener to the component. With this configuration:
- The user enters a valid username and password.
- The LoginScreen fires an ActionEvent.
- The EchoInstance is notified of the event and:
- Determines the next screen.
- Instantiates the appropriate form component.
- Adds itself as a listener to the new form component.
- Sets the new form component as the content of the window.
Alternative Implementations
We could have chosen a different type of event to be fired. The ActionEvent was convenient because it is already defined and allows us to express the information necessary about the screen's behavior. However, some screens present complex behavior and may justify having their own type of event and listener. For example, listeners on the LoginScreen might be interested in failed login attempts as well as successful login attempts. In that situation, we would have two options:
- Use the actionCommand property of the ActionEvent to carry the additional information distinguishing successful and failed attempts.
- Define a new type of event and listener, giving the listener different callback methods for each behavior.
In the context of our LoginScreen, we would apply the first technique by setting the actionCommand property to "loginSuccess" or "loginFailed" appropriately. We would apply the second technique by defining a LoginEvent class and a LoginListener interface with the methods loginSuccessful(LoginEvent) and loginFailure(LoginEvent).
In general, the second option is a little more work, but worth the effort on larger projects.
The Code
A walkthrough of the code changes necessary to migrate the example application to a Model 2 architecture as described above is available for your reference: Migrating to a Model 2 Architecture.
Reusability and Business Logic
Our LoginScreen component uses simple logic to determine if the user credentials are authentic. In a real application, the component would access a business logic class from the middle tier to analyze the credentials. If the LoginScreen instantiates the middle tier class directly, it becomes coupled with the application's middle tier and loses reusability across applications.
The solution is to define an interface that captures the interactions between the component and the middle tier, then add a setter method for implementations of that interface or require the interface in the component's constructor. The main Echo application class then implements the interface, providing a facade to the application's business logic, and provides your component with the implementation at the same time it registers itself as a listener for the form component's events.
Suggested Readings
The articles below have been selected to provide you with additional material about object coupling and Model 2 architecture.
Objects and External Coupling
What Does Object-Oriented Design Mean to You? (Coupling is discussed near the bottom.)
Coupling and Cohesion
Interfaces and Loose Coupling in Java
Model 2 Architecture
Most of the information available about Model 2 architectures concerns JSP pages. However, you have seen how EchoStudio's form components and the EchoInstance are analogous to JSP pages and a controller Servlet for purposes of understanding navigation logic architectures.
J2EE Blueprints: Structuring the Web Tier
Almost All Java Web Apps Need Model 2
Understanding the JavaServer Pages Model 2 Architecture
|