Sunday, January 25, 2015

CDI events in Swing application to decouple UI and event handling

After having the pleasure building my code around CDI for couple of years, it feels very natural to use it to structure my code according to well-known patterns. CDI is a dependency injection mechanism designed to be used within Java EE application servers, and this could be perceived as a disadvantage. However, I want to show that it can be used and has great potential also in a Java SE application.

What is great about CDI is that it is much more than an injection mechanism. On top of this it provides also an elegant and powerful event passing mechanism. This feature can be nicely combined with Swing to build a GUI application based on MVC pattern.

It is really possible to efficiently combine CDI and Swing framework to build a Java GUI application rapidly and with a clear structure. Stay tuned to find out how...

First of all, the reference implementation of CDI called Weld, is distributed also as a separate library. You may add it to your project and start using it. The only shift from the standard way of running the application is that you need to start a Weld container, which is as simple as this one liner:


import org.jboss.weld.environment.se.StartMain;
public static void main(String[] args) {    StartMain.main(args);
}


To add Weld into your maven application, just add this dependency: org.jboss.weld.se:weld-se:2.2.9.Final

To execute your application code, you should put it in a method which observes ContainerInitialized event:

public void start(@Observes ContainerInitialized startEvent) {
   // code which would be usually in the main() method
}

In the method above, you may initialize your application, build and display the GUI and wait for Swing events.

And here starts the interesting part. I will use CDI event mechanism to implement binding between Swing components and the model using observer pattern. The idea is to trigger custom events whenever a data update should occur and not modify data directly. The controller observes triggered events and executes actions based on the event data. The actions then manipulate the datamodel and send notifications to the view about data updates. See following diagram:



The MVC cycle starts in Swing action listeners, which compose an action object and emit it as a CDI event. The action listener is not bound to any controller code - the controller is bound to event using the CDI mechanism. This completely decouples GUI code from business logic. Following snippet responds to button click event and emits an action to add a value to a counter:

@ApplicationScoped
class MainFrame extends javax.swing.JFrame {
  @Inject Event<ChangeValueAction> changeValueAction;
...
  void addButtonActionPerformed(java.awt.event.ActionEvent evt) {
    changeValueAction.fire(ChangeValueAction.plus(getValue()));
  }
...
}

Here we need to remember that observers of CDI events would be created as new objects for any event triggered, together with all dependencies. I used @ApplicationScoped for MainFrame to ensure that all the code operates on the very same instance of it.

One thing to mention here: in order for CDI to work, instance of MainFrame must be created by CDI, and not using its constructor directly. This is achieved by injecting it to already existing bean - e.g. the one, which observes ContainerInitialized event emitted at start-up.

CDI mechanism dispatches the event to any observer method, which listens for this type of event. We create a controller Application and put the code into an observer method, like this:

public class Application {

...
  public void updateValueWhenChangeValueAction(@Observes final ChangeValueAction action) { 
   ... // controller action
  }
...
}

Finally, the controller updates the model and triggers update of the view if necessary. If we take it further, we may trigger an update event from the controller, which would be observed by the view, in this case the MainFrame component. Or even build a model, which automatically triggers CDI events on update. Thus, controller and view would be completely decoupled and only respond to events - GUI events flowing in the direction from View to Controller, and data-update events flowing from Controller/Model to View.

In summary, CDI event mechanism is very convenient for building an MVC Swing application with View decoupled from business logic. This can be accomplished by running your application inside the Weld CDI container (1 line of code), triggering actions from Swing listeners (2 lines of code) and observing the actions (single method on any CDI-enabled class). The actions take a form of a data bean, which itself is not too many lines of code altogether.


A complete example can be found on github: https://github.com/OndrejM/JavaDecoupledUI-CDI



Additional resources:

1 comment: