Skip to content

xemantic/xemantic-kotlin-swing-dsl

Repository files navigation

xemantic-kotlin-swing-dsl

Express your Swing code easily in Kotlin

Maven Central Version GitHub Release Date license

GitHub Actions Workflow Status GitHub branch check runs GitHub commits since latest release GitHub last commit

GitHub contributors GitHub commit activity GitHub code size in bytes GitHub Created At kotlin version kotlinx-coroutines version

discord server discord users online X (formerly Twitter) Follow

Why?

Kotlin provides incredible language sugar over pure Java. Using Kotlin for writing Swing UI already makes the code more concise, but what if the Swing was written from scratch, to provide Kotlin-idiomatic way of doing things? This is the intent behind the xemantic-kotlin-swing-dsl library - to deliver a Domain Specific Language for building Swing based UI and react to UI events by utilizing Kotlin coroutines. Historically I started this project when I needed to quickly assemble basic remote control interfaces for my art robots, which are using software stack developed in the we-are-the-robots project.

Usage

Add to your build.gradle.kts:

dependencies {
  implementation("com.xemantic.kotlin:xemantic-kotlin-swing-dsl-core:1.2.14")
  runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.1")
}

⚠️ the kotlinx-coroutines-swing version should be aligned with the version of other coroutine libraries used in your project. If unsure, find the latest version of the kotlinx-coroutines library.

Example

Here is a simple internet browser. For the sake of example, instead of rendering the full HTML, it will just download a content from provided URL address and display it as a text.

import com.xemantic.kotlin.swing.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.awt.Dimension
import java.net.URI

fun main() = MainWindow("My Browser") { window ->
  window.preferredSize = Dimension(300, 300)

  val urlBox = TextField()
  val goButton = Button("Go!") { isEnabled = false }
  val contentBox = TextArea()

  urlBox.textChanges.listen { url ->
    goButton.isEnabled = url.isNotBlank()
  }

  merge(
    goButton.actionEvents,
    urlBox.actionEvents
  )
    .filter { goButton.isEnabled }
    .onEach { goButton.isEnabled = false }
    .map { urlBox.text }
    .flowOn(Dispatchers.Main)
    .map {
      try {
        URI(it).toURL().readText()
      } catch (e : Exception) {
        e.message
      }
    }
    .flowOn(Dispatchers.IO)
    .listen {
      contentBox.text = it
      goButton.isEnabled = true
    }

  BorderPanel {
    north {
      Border.empty(4) {
        BorderPanel {
          layout { gap = 4 }
          west { Label("URL") }
          center { urlBox }
          east { goButton }
        }
      }
    }
    center { ScrollPane { contentBox } }
  }
}

When run it will produce:

example app image

Notable conventions

  • No J prefix in UI component names (historically J was added to differentiate Swing components from AWT components and is mostly irrelevant for modern purposes).
  • Main JFrame is created with the MainWindow builder, which also takes care of setting up the SwingScope holding a coroutine scope bound to Swing's event dispatcher thread.
  • Instead of event listeners (callbacks), events are delivered through Flows. The listen() function collects the flow in the newly launched coroutine, which is cancelled when the window is closed.
  • Other coroutine dispatchers, like IO, can be used in the event processing pipeline by adding the flowOn. This makes the cumbersome SwingWorker obsolete.
  • Each UI component can be immediately configured with direct access to its properties.
  • Panels are specified with special builders, allowing intuitive development of the component tree.

Key benefits

  • compact UI code with minimal verbosity
  • declarative instead of imperative UI building
  • reactive event handling using Flows,

The code above is taken from the MyBrowserKotlin demo.

How would it look in pure Java Swing?

For the sake of comparison the MyBrowserJava demo implements exactly the same "browser" in pure Java.

Other examples

The demo folder contains additional examples.

Testing

This library is also supporting testing of presenters, if your code adheres to Model View Presenter principles. You can check an example in the mvp-presenter demo module.

Add this dependency to your build.gradle.kts for testing support:

dependencies {
  testImplementation("com.xemantic.kotlin:xemantic-kotlin-swing-dsl-test:1.2.14")
}