The Programmers Guide To Kotlin - Using Swing
Written by Mike James   
Monday, 26 February 2024
Article Index
The Programmers Guide To Kotlin - Using Swing
Getters & Setters
Swing Events
The Program

Getters & Setters

If the Java method follows the convention for get and set properties then you can simply use them as if they were Kotlin properties. Note, however, that this only works for methods starting with get and no parameters, or starting with set and a single parameter.

Interestingly Kotlin can also deal with Booleans that are named starting with is and the setter starts with set as properties.

So, in our previous example, we can't treat setSize as a property because it takes two parameters, but we can treat isVisible as a property as it is Boolean with a name starting with is:

myFrame.isVisible=true

You can use the Java get and set functions but notice that IntelliJ will list all of the properties that it has converted from Java get and set functions. So if you can't find setTitle in the dropdown autocomplete list, look for a Title property:

myFrame.Title="New Title"

is the same as:

myFrame.setTitle("New Title")

If you are familiar with the Java classes you are using you will sometimes find this confusing.

The Event Dispatch Thread

Now we have to tackle one of the most confusing parts of using Swing, or any GUI framework.

In the previous example we created the JFrame using the main thread i.e. the thread of execution that is used to run the main function. This is not a good idea. The reason is that when you use Swing another thread is automatically created and this handles all of the events that can be generated by buttons and other components.

This thread is the Event Dispatch Thread or EDT and if you create your JFrame using the main thread, then potentially two different threads will try to interact with it. This might work for a while, but Swing is not thread-safe and sooner or later something strange will happen as two different threads compete to update the UI.

The correct way to work with Swing components is to use the EDT to create and manipulate all of them. The main thread, or any other thread, should never try to access a Swing component.

So how do you run code on the EDT?

This is fairly easy. There are utility functions in SwingUtilities that will take any code you pass to it and run it on the EDT. The exact mechanism is that the code is added to the event queue, and after all the events that were ahead of it have been processed the EDT will run it. 

There are two utility functions that will run code on the EDT - invokeLater which adds the code to the event queue and returns immediately and invokeAndWait which adds the code to the event queue and doesn't return until the code has completed. Both accept a Runnable and this is particularly easy to arrange in Kotlin, but first we need to look at how lambdas are converted into SAMs.

SAM Conversions

A SAM is a class or Interface with a Single Abstract Method. They are what Java often uses to allow you to create something that looks like a function. When a function is needed all you do is create an instance of the SAM that represents it and implement the method that stands in for the function.

For example, Java has the Runnable interface with the single abstract method run. This is the method that you implement to define code that should be executed on a different thread. That is, you implement Runnable and the run method and then use:

Thread t=new Thread(myRunnable)
t.start()

to execute the code you created in the run method on a new thread.

This isn't difficult once you understand it, but having to create an instance of a class just to pass a function to another function is verbose and it is what lambdas were invented to make easier.

The Kotlin compiler will automatically recognize when you are passing a lambda as a parameter to a method that requires a SAM, defined as an Interface, to be passed and will convert the lambda to a SAM.

That is, it takes the code in your lambda and wraps it in an instance of the SAM automatically without you having to do anything.

For example you can write the previous use of Runnable as:

val t=Thread({System.out.println("Hello New Thread")})
t.start()

Notice that the parameter is a lambda in curly brackets. Using the rule that a final lambda parameter can be passed outside of the function's parentheses we can write this as:

val t=Thread {System.out.println("Hello New Thread")}
t.start()

A small change but one that makes the code easier to read. When the lambda is passed to Thread it is automatically wrapped in an anonymous class which implements Runnable. This also means that the type that Java sees for the lambda depends on what method it is passed to. 

If there is more than one possibility, or you simply want to make it clear which SAM the lambda is converted into, you can explicitly use an adapter function. These are usually generated by the compiler.

For example you can convert a lambda to a Runnable you would use:

val run:Runnable =  Runnable{
System.out.println("Hello New Thread")}
val t=Thread (run)
t.start()

If you are using IntelliJ then the autocomplete will list the Java methods that accept SAMs and show you them converted into the lambda form ready for you to make use of.

Note that this automatic conversion only works for SAMs defined using an Interface and not a virtual class. Fortunately most of the important SAMs are defined as interfaces so that the classes that use them can implement more than one.



Last Updated ( Monday, 26 February 2024 )