Kotlin Primer
The Part Before the Next Part
Before we go into some possibly unknown terrortory, I want to approach it from an angle that might feel familiar. Then terrorize you after. You can thank me later.
The Observer Pattern
I used to teach Design Patterns at Johns Hopkins, so I really like using them where it makes sense. This is a such a case, and we'll use a fairly simple pattern, Observer, to work our way into some more advanced Kotlin concepts that you'll need to be familiar with.
The Observer pattern involves two parties:
- Observer - a thing that wants to be informed when something happens
- Observable - a thing that allows you to register Observers, and informs them when something happens
You've very likely used the Observer pattern when writing a user interface in Java:
JButton button = new JButton("Press Me");
button.addActionListener(...);
In this case, the JButton is the Observable, and the ActionListener is the Observer.
Let's look at this in more detail in Java, with a simplified Button class. Note that this example is not thread safe to keep it simpler.
First, an Observable Button class:
interface OnClickListener {
void onClick(Button button);
}
class Button {
private OnClickListener onClickListener; // initial value is null
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
private void internalClickLogic() {
// watches for gestures that indicate a click on this button
// when clicked:
if (onClickListener != null) { // if we have a listener (observer)
onClickListener.onClick(this); // inform it we were clicked
}
}
}
The internalClickLogic() method is some hand-waving to hide the logic the button must perform to
determine if it has been clicked.
To use this button, we need to create a listener (observer) and register it. When the button is pressed, it
will call the listener's onClick() function to notify it.
class MyClickListener implements OnClickListener {
@Override
public void onClick(Button button) {
System.out.println("Button Clicked!");
}
}
public class JavaMain {
static void main() {
Button button = new Button();
button.setOnClickListener(new MyClickListener());
}
}
Let's see what this all looks like in Kotlin:
interface OnClickListener {
fun onClick(button: Button)
}
class Button {
var onClickListener: OnClickListener? = null
private fun internalClickLogic() {
// watches for gestures that indicate a click on this button
// when clicked:
onClickListener?.onClick(this) // inform it we were clicked
}
}
class MyClickListener : OnClickListener {
override fun onClick(button: Button) {
println("Button Clicked!")
}
}
fun main() {
val button = Button()
button.onClickListener = MyClickListener()
}
Pretty straightforward, and we used some Kotlin idioms in the translation:
onClickListeneris avarproperty, so we didn't need a separatesetOnClickListener()onClickListener?.onClick(this)eliminates the need for theifcheck
Anonymous inner classes
- That
MyClickListenerwill be highly contextual, and likely only used once. Here we're polluting the containing package with an extra class name. Java came up with "anonymous inner classes" to fix this:
public class JavaMain {
static void main() {
Button button = new Button();
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(Button button) {
System.out.println("Button Clicked!");
}});
}
}
This is quite clunky looking, but you get used to it... When you see
new SomeInterface() { ... } this is defining a new, nameless class, that implements that
interface, and the implementation details are inside the { ... }. This also works for
any non-final class in Java.
Note
If the interface defines a single method, this is known as a "Single Abstract Method"
interface, aka "SAM Interface". When I implement a SAM interface as an anonymous inner class,
I like to squash the trailing }}); together. Why? Because it's ugly. Really ugly. Ugly
enough that it draws attention, which helps identify the construct. YMMV...
But now I code entirely in Kotlin, so it's a moo point; cows disagreeing. Thanks, Joey. Howyoudoin?
This really cleaned up namespace pollution, and made it more obvious what's happening, as the "what to do" is right there, inline. This is especially important when you have lots of Observers in play, so you know which is which.
How do we do this in Kotlin? It's a pretty direct translation:
fun main() {
val button = Button()
button.onClickListener = object: OnClickListener {
override fun onClick(button: Button) {
println("Button Clicked!")
}
}
}
The object keyword describes classes that are only used once, otherwise known as Singletons.
We'll be seeing much more of it in this course. It can be used in this case, to define an anonymous
class, but it can also be used on top-level classes to indicate that only a single instance will
ever exist.
But this leads to an interesting question... If we're only using the MyClickListener or
anonymous inner class to host a single method/function do we really even need the class? Why not
just specify the function?