The state of JVM desktop frameworks: Swing
Tags: kotlin, swing, gui, desktop
Published: 2021-01-17 16:58:52

In the first post of this series, we went through the rise and fall of some of the desktop frameworks, mainly Java ones. This post and the following will each focus on a single JVM framework. To compare between them, a baseline is in order. Thus, we will develop the same application using different frameworks. We will use the Kotlin language because some of the frameworks require Kotlin.

This post focuses on the Swing API.

## A sample application

We need a simple application to develop for comparison purposes. The Web offers a lot of application ideas:

* A TODO list
* A quiz
* A calculator
* A client over a web API _e.g._ Reddit, Twitter, etc.
* Conway's game of life
* etc.

Years ago, I developed a custom dice-rolling application for the Champions role-playing game. But it's complex and requires a non-trivial amount of work.

In the past, when I tried [Griffon](http://griffon-framework.org/), I used a _file renamer_ sample. In short, it allows to select a folder and to run a batch rename command on its child files with the help of a regular expression. The wireframe looks like the following:

![File Renamer application wireframe](https://dev-to-uploads.s3.amazonaws.com/i/s49evg4hovjmr4on7fus.png)

User interactions with the application are as follows:

<table>
  <thead>
    <tr>
      <th>Event
      <th>Action
  <tbody>
    <tr>
      <td>Application starts
      <td>Fill the <code>Folder</code> field value with the path to the current user's home
    <tr>
      <td>Button <code>Browse</code> clicked
      <td>
        <ul>
          <li>Open a new File Browser popup.<br>
              When the user chooses a folder, replace the <code>Folder</code> field value with the folder's path
          <li>Refresh file candidates name
    <tr>
      <td><code>Folder</code> field value changed
      <td>Refresh file candidates name
    <tr>
      <td><code>Regex</code> field value changed
      <td>Refresh file candidates name
    <tr>
      <td><code>Replacement</code> field value changed
      <td>Refresh file candidates name
    <tr>
      <td>Button <code>Apply</code> clicked
      <td>Rename files
  </tbody>
</table>  

A couple of other rules apply:

* When the File Browser popup opens, it's initialized with the `Folder` field value.
* In the table, if the candidate name is different from the current name _i.e._ if the renaming changes the name, paint the cell's background in yellow
* One can select folders, but not files

The benefit of this sample is two-fold: the business logic is quite limited, while the GUI has enough behavior to be of interest.

## A quick overview of Swing

Giving the age of Swing, a lot of material is readily available on the Web. Still, let's have a quick overview of the Swing framework.

### Before Swing

Swing is not the first Java framework. This honor belongs to <abbr title="Abstract Windowing Toolkit">AWT</abbr>.

AWT is the first GUI framework, available since 1.0. It's a thin abstraction layer on top of the system-specific graphical objects. For this reason, an AWT application displays - and uses - the platform's Look-And-Feel. AWT controls are thus called _heavyweight_ ones in Java.

In the 1.2 release, Java offered Swing. While AWT relies on OS graphics, Swing paints every component itself. To do so, it relies on the Java 2D Graphics library. Java 2D and Swing make up the Java Foundation Classes.

Because it's independent of the OS, Swing provides two main benefits compared to AWT:

1. A large catalog of widgets. AWT is limited by the set of widgets that are available on all OS providing a JDK. Having no such limitations, Swing can offer any widget.
2. OS-independent <abbr title="LAF">Look-And-Feel</abbr>. The OS LAF constrains AWT. Because Swing implements the painting of widgets, it can (and it does) provide pluggable LAFs. For example, Metal is the basic OS-independent one hut it also provides several LAFs based on specific OS.

    You can set the LAF at startup time and change it dynamically during the application lifecycle.

    You can also design a custom LAF - though it's not trivial.

On the flip side, a Swing application consumes more memory than an AWT one.

### Components overview

The Swing class hierarchy is pretty standard for developers. An _abstract_ `JComponent` class is the parent. A `Container` class represents a component that has children components.

![Swing components' hierarchy class diagram](https://dev-to-uploads.s3.amazonaws.com/i/ew9umreuxdv17z84orez.png)

As the diagram shows, Swing makes use some of the <abbr title="Abstract Windowing Toolkit">AWT</abbr> classes.

This theoretically allows you to mix components of both frameworks. Yet, you should avoid doing this in general since the mix of AWT components, which are heavyweight, with (lightweight) Swing components, can end in odd behaviors.

### Events

Swing offers a full-fledged event system. It's based on the classical _Observer_ pattern.

For example, here's a simplified view of the `JButton` class diagram for the `ActionListener`:

![Swing JButton event simplified class diagram](https://dev-to-uploads.s3.amazonaws.com/i/98gvvyjujyvin0k4oriz.png)

### Layout

Classical layouts are available, such as `BoxLayout` - which can be either vertical or horizontal - and `GridLayout`. It's possible to design any application by combining them.

For most applications, the nesting of layouts will be too deep. A powerful alternative to nested layouts is the `GridBagLayout`: it allows the precise placement of components on a parent container.

![Simplified layout class diagram](https://dev-to-uploads.s3.amazonaws.com/i/9314zvz8yrdsax2tdywv.png)

Swing offers a unified API across all available layouts. Hence, the second parameter of the `Container.add()` method is of type `Object`. For example, on a `Container` with a `GridBagLayout`, the second parameter needs to be a `GridBagConstraints` object.

Of course, generics would allow stricter type-checking. But Swing was designed a long time before generics and the API never did use them - and never will because of Swing's status.

In Java, the code is boilerplate-y. Kotlin can improve it, thanks to extension functions, and named/default arguments.

```kotlin
private fun constraints(
  gridx: Int = 0, gridy: Int = 0, gridwidth: Int = 1, gridheight: Int = 1,
  weightx: Double = 0.0, weighty: Double = 0.0, anchor: Int = CENTER,
  fill: Int = NONE, insets: Insets = Insets(0, 0, 0, 0),
  ipadx: Int = 0, ipady: Int = 0) = GridBagConstraints().apply {            // 1
  // Apply the parameters
}

private fun JPanel.add(vararg components: Pair<JComponent,                  // 2
                       GridBagConstraints>) {
  components.forEach {                                                      // 3
      add(it.first, it.second)
  }
}

JPanel().add(
  JLabel("Folder:") to constraints(insets = Insets(4, 4, 4, 0)),            // 4
  DirectoryTextField to constraints(gridx = 1, fill = HORIZONTAL, weightx = 1.0, gridwidth = 2), <4>
  FolderPickerButton to constraints(gridx = 3, anchor = LINE_END, insets = Insets(4, 0, 4, 0))  <4>
)
```

1. Allow to define non-default values
2. Accept any number of arguments
3. Loop over the pairs to add them
4. Add component with defined constraints

## Lessons learned

Here are the lessons I learned, in no particular order:

### Event Bus for the win

This is not specific to Swing: introducing an _Event Bus_ between _event producers_ and _event listeners_ allows to decouple the latter from the former. If you're interested to read more about the Event Bus, please read a [previous post](https://blog.frankel.ch/my-case-for-event-bus/) on the subject.

In the sample, the Event Bus implementation is [Green Robot](https://greenrobot.org/eventbus/).

### Event model

To rename, the btn:[Apply] button needs data: the path, the regex, and the replacement. These data are available in the three different text fields.

How can one access these data in the button? At least two different ways are available:

1. Store a reference to the fields in the button
2. Each time a field value changes, send an event with the new value, make the button listen to those events, and store event values

Moreover, when the value of either the _Regex_ or the _Replacement_ text fields changes, the application needs to refresh the right column of the table.

My preference goes to the second option to decouple components from each other.

This is the flow's representation:

![Renamer application sequence diagram](https://dev-to-uploads.s3.amazonaws.com/i/gm6cyiypq1qwdjidgbgp.png)

`PathModel` is a singleton that has no GUI associated. With the help of the Event Bus, it allows us to have a single place to listen to events, and dispatch them afterward.

### Components are not scrollable

While nobody expects text fields or buttons to be scrollable, it's a different matter for text boxes and tables. But by default, no Swing component is scrollable. To make such a component scrollable, one needs to embed it in a `JScrollPane` component.

One can customize the scroll pane in a fine-grained way, but it works out-of-the-box with `JTable`. For example, column headers are always visible by default.

![JScrollPane class diagram](https://dev-to-uploads.s3.amazonaws.com/i/to183ljpbmq2hrtuuyke.png)

### Text field model and events

Swing decouples components from the data they display:
`JTable` store its data in a `TableModel`, `JComboBox` in a `ComBoxModel`, `JTextField` in a `Document`, etc.

Regarding events, models are the objects to listen to.
For example, `Document` offers fine-grained events when its content changes:
proper change, insertion, and removal.

The following class diagram is a summary:

![Summary of JTextField class diagram](https://dev-to-uploads.s3.amazonaws.com/i/4935rm0657clzfn24mbe.png)

In the sample app, different text fields need to send events when their content changes. By taking advantage of Kotlin, it's possible to handle this in a centralized place:

```kotlin
abstract class FiringTextField<T>(private val eventBus: EventBus,
                                  private val create: (String) -> T) : JTextField() {

  private val Document.text: String
    get() = getText(0, length)

  init {
    document.addDocumentListener(object : DocumentListener {
      override fun changedUpdate(e: DocumentEvent) = postChange(e) // 1
      override fun insertUpdate(e: DocumentEvent) = postChange(e)  // 1
      override fun removeUpdate(e: DocumentEvent) = postChange(e)  // 1
    })
  }

  private fun postChange(e: DocumentEvent) =
    eventBus.post(create(e.document.text))
}

object ReplacementTextField : FiringTextField<ReplacementUpdatedEvent>(
    EventBus.getDefault(),
    { ReplacementUpdatedEvent(it) }
)
```

1. Handle each change in the same way

### Threading

The Swing threading model is easy to get wrong. The most important rule is that **no long-running task should ever run on the main event thread**, known as the _Event Dispatch Thread_.

> Tasks on the event dispatch thread must finish quickly; if they don't, unhandled events back up and the user interface becomes unresponsive.
>
> -- [The Event Dispatch Thread](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html)

Two approaches are possible:

1. Make all subscriptions to the event bus asynchronous. The Event Bus calls methods triggered in this way on a dedicated thread, not on the <abbr title="_Event Dispatch Thread_">EDT</abbr>. In that case, the developer needs to explicitly run the code on the EDT when necessary.
2. Keep the default synchronous behavior. Posting events on the bus fits the definition of "finishing quickly". But then, a `Runnable` needs to wrap long-running tasks. One can use `SwingUtilities.invokeLater()` to start it.

In the sample app, I favored the second approach, as renaming is the only task that can potentially run for "a long time".

### Additional takeaways

* There's no need to design a widget to choose files from scratch. Swing offers the `JFileChooser` class that displays such a configurable widget.

    ![Screenshot of the JFileChooser](https://dev-to-uploads.s3.amazonaws.com/i/imbxdwy6kj5mib9m4jkr.jpg)

* Objects need to collaborate. We have already written about the Event Bus to manage _runtime_ events. To assemble components, there are other ways: Dependency Injection is a pretty popular one. For a simple application, even more so a GUI one, singletons are more than enough. For example, the `TableModel` and the `JTable` can be singletons:

    ```kotlin
    object FileModel : AbstractTableModel() {
      // Initialization
    }
    
    object FileTable : JTable(FileModel) {
      // Initialization
    }
    ```

## Conclusion

While quite old, Swing still gets the job done. It's the baseline with which we will compare other approaches.

If you're interested, you can run [the application](https://github.com/ajavageek/renamer-swing/packages/377965) by yourself.

Thanks to [Hendrik Ebbers](https://twitter.com/hendrikebbers) for his kind review.

The complete source code for this post can be found on Github in Maven format: {% github ajavageek/renamer-swing %}

**To go further:**

* [The Swing Tutorial](https://docs.oracle.com/javase/tutorial/uiswing/)
* [Swing JavaDocs](https://docs.oracle.com/en/java/javase/15/docs/api/java.desktop/javax/swing/package-summary.html)
* [Swing debugging tips](https://docs.oracle.com/en/java/javase/15/troubleshoot/swing.html)

_Originally published at [A Java Geek](https://blog.frankel.ch/state-jvm-desktop-frameworks/2/) on January 17<sup>th</sup> 2021_