Kotlin from the Ground Up (Part 5)

Mike Warner
30 min readJul 26, 2022

--

Click here to go back to the start of the series where you can view the TOC.

In this part we’ll dive into the Kotlin language and some of its main features.

What does this part cover:

  1. Diving into the Kotlin language (introduction)
  2. Importing packages
  3. Classes, interfaces, properties and methods
  4. Beans (Javabeans)
  5. The Main Function
  6. For loops
  7. Extensions
  8. Working with Collections
  9. Scope Functions
  10. Data Classes and Enums
  11. Mutable vs Immutable
  12. ̶C̶o̶m̶p̶a̶n̶i̶o̶n̶ ̶O̶b̶j̶e̶c̶t̶s̶ (won’t cover anymore)
  13. ̶L̶a̶m̶b̶d̶a̶s̶ ̶&̶ ̶H̶i̶g̶h̶e̶r̶-̶O̶r̶d̶e̶r̶ ̶F̶u̶n̶c̶t̶i̶o̶n̶s̶ (won’t cover anymore)
  14. ̶F̶i̶l̶e̶ ̶I̶/̶O̶ (won’t cover anymore)
  15. ̶R̶e̶s̶o̶u̶r̶c̶e̶s̶ ̶&̶ ̶S̶e̶t̶t̶i̶n̶g̶s̶ (won’t cover anymore)
  16. ̶C̶o̶r̶o̶u̶t̶i̶n̶e̶s̶ (won’t cover anymore)
  17. ̶D̶e̶p̶e̶n̶d̶e̶n̶c̶y̶ ̶I̶n̶j̶e̶c̶t̶i̶o̶n̶ (won’t cover anymore)

Diving into the Kotlin language

This is not meant as an exhaustive overview of all of the language’s features, that is what the Kotlin reference docs are for. Instead, we’ll take a tour through the main features by building an example application, whilst giving enough context about the concepts so that you can find them in the reference when you need them.

Let’s first re-state our task:

We are building a simple program to control Philips Hue Lights. We’ll provide an alternative solution (NOOP) for people that don’t have those lights, which will just print out the values instead.

Before we start implementing our project and diving into syntax and constructs, let’s first clean up the project we created in Part 4:

  • Delete gradlew.bat if you are not working on Windows
  • Comment out the assertion in src/test/kotlin/com/example/AppTest.kt
  • We’ll be working on the tests later on, so we’ll come back to that file
  • We’ll start coding everything in App.kt, and then extract the classes/functions into separate files towards the end. The purpose of this is to allow us to follow the code more easily.

Entry Point

Start by erasing comments and the App class in src/main/kotlin/com/example/App.kt to start with an empty slate:

package com.examplefun main(args: Array<String>) {
}

Notice there was already a Class in there. There are multiple types of classes in Kotlin, such as data classes, enum classes, sealed classes, amongst others. I won’t do any justice in repeating the official docs here, so I suggest you browse through them here, instead I’ll provide examples later on.

Notice how the type of the parameter is after the parameter name, as opposed to Java where it is usually stated before.

Functions in Kotlin are denoted by the fun keyword. I couldn’t find official docs that indicate why fun was chosen as a word, other than the fact it is short and memorable.

Importing Packages

Add the following imports just below package declaration:

import io.github.zeroone3010.yahueapi.Hue
import io.github.zeroone3010.yahueapi.Light
import io.github.zeroone3010.yahueapi.Room
import io.github.zeroone3010.yahueapi.State
import io.github.zeroone3010.yahueapi.StateBuilderSteps.OnOffStep

We’ll be using them later on. An easy way to import things is to do so as you go. If you try to use Hue or Light, IntelliJ will colour undefined classes/methods in red, if you press option+enter (alt+enter) at the end of the method/class name it will offer to import it from an available library.

If you cmd+click (or ctrl+click) the Hue name in IntelliJ you’ll be directed to the class where you’ll see it is a Java final class, meaning it can’t be extended.

As we want to build a program that works for people with and without Hue lights, we’ll make a wrapper for it instead.

Classes, Interfaces, Properties and Methods

Let’s start by creating two exception classes. Paste the following two lines below your imports (leave one blank line between the imports and the class/function definitions for readability).

class NotInitializedException(message: String): Exception(message)
class IllegalOperationException(message: String): Exception(message)

We have now created two types of exceptions that take in one parameter in the constructor (a message of type String), and both classes extend the Exception base class (which takes message as the parameter).

If we wanted to implement custom properties/methods on our exception classes, you could add { } after : Exception(message) and start coding away. In our case we just want to extend Exception directly. The reasoning is we can now throw these specific exceptions, and catch them explicitly if needed (instead of “pokemon catching” all Exceptions).

Throwing Exceptions

Now we can throw and catch custom exceptions. This is an example of how you would do this in Kotlin:

fun main(args: Array<String>) {
try {
throw NotInitializedException("NIE Thrown!")
} catch (exception: NotInitializedException) {
print("NIE Caught: $exception")
}
}

Very similar to how you would do this in other languages. Notice however how you don’t have to add a new keyword before the class declaration like in PHP.

If you tried out the previous code, go ahead and remove everything inside the main function again.

Interfaces

Paste the following code just below our two “exception” class definitions:

interface HueFactoryInterface {
var ip: String?
var apiKey: String?
var appName: String?
var hue: Hue?

fun initializeHue() {}
fun getRooms(): List<Any>
fun getLights(room: Room): List<Any>
fun getLights(room: String): List<Any>
fun turnOn(room: Room) {}
fun turnOn(light: Light) {}
fun turnOn(text: String) = println("Turning on $text")
fun turnOff(room: Room) {}
fun turnOff(light: Light) {}
fun turnOff(text: String) = println("Turning off $text")
fun switchColor(room: Room, color: java.awt.Color) {}
fun switchColor(light: Light, color: java.awt.Color) {}
fun switchColor(text: String, color: java.awt.Color) = println("Set $text to $color")
fun checkInitialization() {
if (this.apiKey == null || this.hue == null) {
throw NotInitializedException("Hue not initialized")
}
}
}

Just as in other languages, we have interfaces and abstract classes. I opted for an interface for our example as we don’t need to hold state, but we do want to provide some default implementations of functions (to reduce the amount of code in our NOOP class).

Our interface is a blueprint for our two classes: One of them will perform actions on the Hue Bridge, and the other will simply print out the actions.

The first 4 lines are our properties. The var keyword indicates they are mutable (as we’ll set them in constructors, or during initialisation of the bridge). The question mark at the end of the type indicates these are nullable. They are not initialised (as it is an interface) so the default value will be null, unless specified otherwise in the class implementations.

Afterwards we have a list of our functions, some of them have default implementations, some of them do nothing by default, here is a list:

  • initializeHue() → Default implementation does nothing (NOOP)
  • getRooms() → Implemented in each class, returns a list of rooms
  • getLights() → Implemented in each class, returns a list of lights
  • turnOn() → Default is NOOP for lights/rooms, but prints strings
  • turnOff() → Default is NOOP for lights/rooms, but prints strings
  • switchColor() → Default is NOOP for lights/rooms, but prints strings
  • checkInitialization() → Has a default implementation with init check

You’ll notice I tried to provide default implementations for the “String” based ones, which just print the result, to shorten the amount of functions we need too override in our NOOP class we’ll be implementing next…

Classes

Add this import below the other imports: import kotlin.random.Random

Now we’ll implement our NOOP class. Copy & paste the following code:

class HueFactoryNoop() : HueFactoryInterface {
override var ip: String? = "127.0.0.1"
override var apiKey: String? = null
override var appName: String? = "NoopApp"
override var hue: Hue? = null

constructor(vararg params: String?) : this() {
params.forEach { println("Set: $it") }
}

override fun getRooms(): List<String> = listOf("Room 1", "Room 2")
override fun getLights(room: Room): List<Room> = listOf()
override fun getLights(room: String): List<String> {
val randomValues = List(Random.nextInt(0, 5)) {
Random.nextInt(1, 100)
}
return sequence {
for (int in randomValues) {
yield("Light $int")
}
}.toList()
}
}

This code could be way simpler, but we’re doing it like this to illustrate a few concepts like yield/sequences, and the fact you can use for {} as well as forEach.

Let’s analyse the code:

Our class definition is class HueFactoryNoop() : HueFactoryInterface, which means it implements that interface, inheriting its default methods, which also means we need to implement all other methods (using override), and assign the properties it defined.

The first four lines override the property declarations in our interface. We need to override them as we’ll initialise them in this class. After the type, you’ll notice we initialise them with default values, e.g. = "127.0.0.1".

Then we have our constructor. There are multiple ways of using constructors in Kotlin.

Our primary constructor is defined outside the body, after the class name:
class Person constructor(firstName: String) { /*...*/ }

The primary constructor can’t have any logic, only properties.

Alternatively (or in combination) we can also have secondary constructor(s) which can contain code, like we did in our code above. You can also use init blocks that run code upon initialisation. The official docs explain this in more detail.

Our constructor looks like this:

constructor(vararg params: String?) : this() {
params.forEach { println("Set: $it") }
}

It receives a variable list of items as input. It returns an instance of itself, denoted by : this(), and then it loops over the parameters it received, printing out each variable that was passed in.

There are multiple ways of iterating over items in a collection, we’ll cover this later on.

When we are in a forEach block, the it variable represents the current item we are iterating over, but we can also give it an explicit name like this:
params.forEach { item -> println("Set: $item") }

The next line looks like this:
override fun getRooms(): List<String> = listOf(“Room 1”, “Room 2”)

This is a shorter way of defining simple methods that just return something! Alternatively you would have written:

override fun getRooms(): List<String> {
return listOf("Room 1", "Room 2")
}

Our final function does a couple of interesting things. First we have this:

val randomValues = List(Random.nextInt(0, 5)) {
Random.nextInt(1, 100)
}

You might think this code looks a bit weird. That’s because it involves lambdas again, which we previously encountered when analysing the plugins block in our Gradle section. We will cover lambdas in more detail afterwards, meanwhile let’s try to dissect what is going on in this code.

If you cmd+click (or ctrl+click) on List you can see the method’s signature:

So this specific overload of the List function takes in an integer with the size of the list to generate, and a lambda function which specifies which integer to choose for each position in the list. The result will be a list of size length, where each item’s value is chosen by the lambda function.

In the gradle section I previously mentioned: In Kotlin and Java you usually call functions with (), however, if the last argument (in this case the only one) receives a function, you can skip the parenthesis and simply provide the function body wrapped in {}.

So in our invocation to List we are passing one argument in parenthesis (the size, which is chosen by Random.nextInt() (click here for more info on random numbers), and then in curly brackets we specify the function, as it is the last argument we can do this outside…

However, this would be equally valid code (and might help explain the syntax a bit better):

val randomNumberGenerator: (Int) -> Int = {
Random.nextInt(1, 100)
}
val randomValues2 = List(10, randomNumberGenerator)println(randomValues2)

// Output: [78, 75, 69, 90, 2, 94, 45, 6, 77, 83]

Don’t worry if you don’t fully grasp lambdas yet, we’ll have a section about them further on 😁

Finally we have our last code block:

return sequence {
for (int in randomValues) {
yield("Light $int")
}
}.toList()

A sequence code block allows us to “yield” results as needed, which makes the code more readable, also allowing us to populate our list at different points. Alternatively, you could instantiate a new MutableList and append items.

Sequences are actually quite interesting and deserve a deeper read-through, head on over to this section of the official docs if you are curious.

In this example we create a sequence, iterate over each random number we generated, return the same number prefixed by “Light ”, and finally converting the sequence into a list which we return.

You’ll notice this time we used for (int in randomValues) as opposed to randomValues.forEach {}, we could use any of these, just remember in the latter you would have to use $it to reference the current item, or provide a name followed by an arrow.

Beans (JavaBeans)

JavaBeans are a standard for creating self-contained classes, commonly used in the Java world, but by extension used in many Kotlin projects.

The standard specifies these classes should conform to the following 3 rules:

  • They are serialisable
  • They have a zero-argument constructor
  • They allow access to properties using getter and setter methods

This doesn’t automatically mean you have to use the @Serializable annotation provided by the Kotlin Serialization, as it depends on what serialisation library you’ll be using, but in our example we will use it.

A zero-argument constructor is required as these should be initialisable with no state, allowing any module to use your Bean without prior knowledge or context. In our first class above we already do this, as the class has an empty primary constructor (providing no named properties in the constructor), instead we opted for a secondary one denoted by the constructor keyword.

Getters and setters work “out of the box” in Kotlin as we’ll see in a few moments, so we are one line away from being compliant with the standard!

Add @Serializable one line above the HueFactoryNoop class declaration.

This is an annotation, annotations provide metadata for classes or properties. These can be analysed either at build or run time, by your own code or by plugins, and can perform additional actions and code-generation.

In our case this will tell the Kotlin Serialization plugin that it should extend our class with methods allowing serialisation/deserialisation of the object.

You’ll notice a red squiggly line below the line, hovering over it should provide more context. The reason is we still need to import our Serializable plugin. Add these two lines to the imports section at the top:

import kotlinx.serialization.ContextualSerialization
import kotlinx.serialization.Serializable

If you now have a look at the properties of our class, you’ll notice Hue is not serialisable… prefix it with ContextualSerialization to get this working, this is because hue has no explicit serialiser (you can read more about it here). The property declaration should look like this:

@ContextualSerialization override var hue: Hue? = null

We now have a Bean!

Beans are not that crucial when working on simple Kotlin programs, but once you start working with frameworks such as Micronaut, with dependency injection and singletons, they become more important.

But… what about the getters and setters? They are implicit, we can prove this:

class House(var address: String? = null) {
fun getAddress(): String? {
return this.address
}
}

fun main(args: Array<String>) {
val wow = House("Somewhere")
}

This would throw this error:

If you wanted to override the default implementation of the getters and setters for properties, use get() and set() as explained here.

If you tried out the previous code, go ahead and delete the House class, and remove all code from the main function as we’ll carry on building our program.

We’ve created our interface and our NOOP class, let’s code our main factory. We’ll write a new class, making it serialisable from the start…

Copy & paste this code below the HueFactoryNoop class:

@Serializable
class HueFactory() : HueFactoryInterface {
override var ip: String? = null
override var apiKey: String? = null
override var appName: String? = "HueApp"
@ContextualSerialization override var hue: Hue? = null

constructor(ip: String?, apiKey: String?, appName: String?) : this() {
this.ip = ip
this.apiKey = apiKey
this.appName = appName
}
}

So far it looks pretty similar, except that our secondary constructor now contains named properties which we assign to our local properties.

You’ll notice a squiggly line below class HueFactory. If you hover over it you’ll notice the IDE is telling us we need to implement the additional methods specified in the interface.

Now we’ll add our initialisation function which will communicate with the bridge. It will be synchronous for now, so it will block the main thread while it waits for interaction/communication with the bridge, we’ll fix that later on.

Copy & paste this function below the constructor in the HueFactory class:

override fun initializeHue() {
if (this.apiKey?.isNotEmpty() == true) {
this.hue = Hue(this.ip, this.apiKey)
return
}

val apiKey = Hue
.hueBridgeConnectionBuilder(this.ip)
.initializeApiConnection(this.appName)

this.apiKey = apiKey.get()
this.hue = Hue(this.ip, this.apiKey)
}

In our NOOP class we didn’t need to implement this method as our interface had a default one which doesn’t perform any actions.

The way the Hue bridge works is as follows:

  • You connect the bridge to the LAN and it receives an IP via DHCP
  • When writing an app to control the lights you specify the IP of the bridge
  • The first time you try to connect (either via HTTP calls directly, or through a library like we are using) the bridge asks you to press the physical button to receive an API key which you can use for subsequent calls
  • With the API key you no longer need to press the button and can just provide the API key in subsequent requests

Let’s look at our first block of code:

if (this.apiKey?.isNotEmpty() == true) {
this.hue = Hue(this.ip, this.apiKey)
return
}

The first IF is using chaining with a question mark, this is part of null-safety in Kotlin, if any property followed by a question mark is null, that part of the conditional returns null without checking subsequent parts of the chain. In other words if you have the chain person?.name?.isNotEmpty(), and name is null, you will not get an NPE (null pointer exception) when calling isNotEmpty() as the whole chain will “exit early” with null.

So in our conditional, if apiKey was not provided in the constructor, null will not be equal to true, thus skipping this code block. But if it is set, it still checks if it isNotEmpty, and only then will it set the hue property.

If we provided an apiKey then we initialise hue with the Hue library, as specified earlier if we have an IP and an Api key, we are good to start making requests to the Hue bridge, so we can return (exit) at this point.

If we do not have an API key yet, we’ll proceed with Hue.hueBridgeConnectionBuilder(this.ip), to tell the library which IP to use, and then chain it with .initializeApiConnection(this.appName), which will request you press the button (while dumping to STDOUT every second). We’ll keep this is synchronous for now, but we’ll find a way to make in non-blocking later on in this tutorial.

If you are curious about what initializeApiConnection does behind the scenes, cmd+click (or ctrl+click) on it to inspect the Java code:

Let’s get back to our HueFactory class. We’ll add a few “boring methods” which we won’t go too much into detail. These are essentially just the implementations of the other methods, using the Hue library to perform actions (such as getting a list of rooms/lights, and turning them on/off).

Copy & paste the following code below the initializeHue() method:

override fun getRooms(): List<Room> {
checkInitialization()
return this.hue?.rooms?.toList() ?: listOf()
}

fun getRoom(name: String): Room? {
checkInitialization()
return this.hue?.getRoomByName(name)?.get()
}

override fun getLights(room: Room): List<Light> {
checkInitialization()
return room.lights.toList()
}
override fun getLights(room: String): List<String> {
throw IllegalOperationException("I can't retrieve lights from '$room'")
}
override fun turnOn(room: Room) {
checkInitialization()
room.setState((State.builder() as OnOffStep).on());
}

override fun turnOn(light: Light) {
checkInitialization()
light.turnOn()
}

override fun turnOn(text: String) {
throw IllegalOperationException("I can't turn on a '$text'")
}

override fun turnOff(room: Room) {
checkInitialization()
room.setState((State.builder() as OnOffStep).off());
}

override fun turnOff(light: Light) {
checkInitialization()
light.turnOff()
}

override fun turnOff(text: String) {
throw IllegalOperationException("I can't turn off a '$text'")
}

override fun switchColor(room: Room, color: java.awt.Color) {
checkInitialization()
room.setState(State.builder().color(color).on());
}

override fun switchColor(light: Light, color: java.awt.Color) {
checkInitialization()
light.state = State.builder().color(color).on()
}

override fun switchColor(text: String, color: java.awt.Color) {
throw IllegalOperationException("I can't switch the color of a '$text'")
}

You’ll notice the squiggly lines under class HueFactory have disappeared, meaning that we have implemented all our remaining methods from the interface.

You’ll also see we run checkInitialization in every function, which can be avoided in multiple ways… We can simply fail to initialise if not provided at the start, or I can add an annotation and prefix it. In some scenarios it might be warranted, in my example not every function throws the same exception if not initialised.

The first function does this: return this.hue?.rooms?.toList() ?: listOf() , I thought it warranted an explanation:

Again we are using chaining for null-safety, so if hue or rooms are null, toList() will not be executed. Afterwards we have the Elvis operator ?:, which basically is a null-coalesce operator, choosing the left side if it is not null, otherwise returning what is on the right of it. It’s the same as ?? in PHP.

So if hue or rooms are null, the result will be an empty list, otherwise it will be the list of rooms.

But what about listOf, and what are lists and how do they compare to arrays? And what about dictionaries or objects like in Javascript, do we have those? How do they work? Are they performant?

Yes, yes, I hear you. We’ll get to collections in a few minutes 😁, before that we should first aim to get our program up and running.

The Main Function

For all you Pythonistas out there you’ll surely be used to the following code:

if __name__ == "__main__":
# Execute only if run directly
main()

This will execute that function if you call python with your script directly, otherwise it allows you to import the file and use it as a module/class/library.

Similarly in Kotlin we have the concept of an entry point, which can be defined in your Gradle files, but for now we are providing our file to the Kotlin compiler directly, which will check that you have implemented a main() function in the file.

Go to your main function at the bottom (which should still be empty) replace it with the following code:

fun main(args: Array<String>) {
val factory = HueFactoryNoop("192.168.1.5", null, "MyHueApp")
factory.initializeHue()
// println("Hue API key: ${factory.apiKey}")
val rooms = factory.getRooms()
println(rooms)
}

Right now all we are doing is creating an instance of the Noop factory (which will work with strings instead of Hue lights), then we call initializeHue, which won’t do anything in the case of our NOOP class, finally we call getRooms on the factory (which returns a list of a couple of random rooms), and we print the output:

Set: 192.168.1.5
Set: null
Set: MyHueApp
[Room 1, Room 2]
Process finished with exit code 0

The first three “Set” lines come from our constructor in the Noop class which does params.forEach { println(“Set: $it”) } for illustrative purposes.

For loops

Iterators (like for loops, while loops, etc.) can be used on collections that implement the Iterable<T> interface. This includes lists, arrays, ranges, sets, etc. You can find the docs here if you would like to dive deeper.

If you are coming from Python and you want comparisons for the types of loops used there, I can highly recommend this short article.

A very simple iteration over a range would look like this:

for (i in 1..10) print(i)

This will print the numbers 1 to 10 (inclusive).

Let’s start off by iterating over every room, selecting all the lights, turning them on or off, regardless of the current state… A simple chaos monkey 🙈

Replace the line println(rooms) with the following code:

rooms.forEach {
println(it)
}

Now the output of running your program (minus the “Set”s) should be:

Room 1
Room 2
Process finished with exit code 0

You’ll notice in the IDE the it: String label after the opening bracket, this is to indicate that it is set to the current item in rooms per iteration:

If you would have a nested loop, and you plan to use it, you’ll have to make the outer one explicit. In some cases it helps anyway to declare it explicitly to make it obvious what it is, specially when the collection’s name is insufficient (as can be the cases with certain maps sometimes).

This is how you can define it explicitly:

rooms.forEach { room ->
println(room)
}

This is one way of writing a for-each, using the forEach function of an iterable. Another way would be to use a similar notation to the one we used with the range earlier on. Let’s combine both approaches:

rooms.forEach { room ->
println(room)
for (light in factory.getLights(room)) {
println(light)
}
}

You should get something like this if you run it:

Room 1
Light 6
Light 41
Light 56
Light 49
Room 2
Light 88
Light 24

This is because we made getLights (in the NOOP class) return a random list.

You’ll notice we called the factory’s getLights function and provided a room (in this case a string) to return our lights. What if we wanted to call getLights on the room directly to make it a bit easier?

Extensions

This is where extensions come in. We can use extensions to “extend” a class by giving it extra methods and properties.

This includes base classes like String!

Given that our room can either be a String (in the case of Noop) or an actual Room we need to extend both classes to include a getLights function. Add these below the main function (at the end of the file):

fun String.getLights(factory: HueFactoryInterface): List<String> {
return factory.getLights(this) as List<String>
}

fun Room.getLights(factory: HueFactoryInterface): List<Light> {
return factory.getLights(this) as List<Light>
}

We are defining getLights on the Room class itself, providing a factory as input, and the return is a call on the factory to return the lights providing this as input, which is the string (or light) in question! Magic!

However, in the IDE you might have noticed the following warning:

This is due to the fact we are explicitly casting the output to a list of type String (or Light), but it can’t infer this automatically, as our getLights method in the interface returns a List of type Any.

This is why you should be careful when using Any as a type, and try to minimise its usage. The only reason we are doing it in our interface was to allow us to run our program with no actual Hue lights setup, using our Noop class which uses strings instead of room/light objects.

To solve our unchecked cast problem add the @Suppress(“UNCHECKED_CAST”) annotation above each function to solve this (read this for more info). Your two extension functions should now look like this:

@Suppress("UNCHECKED_CAST")
fun String.getLights(factory: HueFactoryInterface): List<String> {
return factory.getLights(this) as List<String>
}

@Suppress("UNCHECKED_CAST")
fun Room.getLights(factory: HueFactoryInterface): List<Light> {
return factory.getLights(this) as List<Light>
}

Now we can invert our code in the main function if we wish:

rooms.forEach { room ->
println(room)
for (light in room.getLights(factory)) {
println(light)
}
}

You’ll notice we now call getLights on the room and provide the factory as an argument.

Let’s actually call a few methods in our forEach (continuing using our HueFactoryNoop instead of the real one). Replace your main function with this block of code:

fun main(args: Array<String>) {
val factory = HueFactoryNoop("192.168.1.5", null, "MyHueApp")
factory.initializeHue()
println("Hue API key: ${factory.apiKey}")
val rooms = factory.getRooms()
rooms.forEach {
println("$it:")
for (light in it.getLights(factory)) {
if (Random.nextBoolean()) {
factory.turnOn(light)
println("- ${light.name} was turned on")
} else {
factory.turnOff(light)
println("- ${light.name} was turned off")
}
}
}
}

You’ll notice ${light.name} will be highlighted as an error in the IDE. This is because in our NOOP way of doing things getLights() will return a list of strings, and String does not implement .name as a getter.

Let’s go ahead and add an extension function for this below the other two extensions:

val String.name: String
get() = this

Wait, but why is this val instead of def? Ok, I lied, this is an extension property. Whenever the .name property is accessed on any String, the getter is called, and in our case we return an instance of this, which is the string itself. We’re adding this to keep compatibility between our NOOP and standard classes.

After adding this extension you can do things like println("Bob".name), which will just return “Bob”.

In our new code in the main function we also added Random.nextBoolean(), this just returns either true or false randomly, every time it is called. You can find more about RNGs in Kotlin here.

If you run the code again you should get the following output:

Set: 192.168.1.5
Set: null
Set: MyHueApp
Hue API key: null
Room 1:
Turning off Light 37
- Light 37 was turned off
Room 2:
Turning off Light 59
- Light 59 was turned off
Turning on Light 51
- Light 51 was turned on

If you remove Noop from the main function, and change the IP, you should see the project still builds, and if you try to run it you might get the following output (if you have a Hue bridge):

192.168.1.6
java.util.concurrent.CompletableFuture@246ae04d[Not completed]
Please push the button on the Hue Bridge now (30 seconds left).
[{"error":{"type":101,"address":"","description":"link button not pressed"}}]
Please push the button on the Hue Bridge now (29 seconds left).
[{"error":{"type":101,"address":"","description":"link button not pressed"}}]
Please push the button on the Hue Bridge now (28 seconds left).
[{"error":{"type":101,"address":"","description":"link button not pressed"}}]
Please push the button on the Hue Bridge now (27 seconds left).
[{"error":{"type":101,"address":"","description":"link button not pressed"}}]
Please push the button on the Hue Bridge now (26 seconds left).
[{"error":{"type":101,"address":"","description":"link button not pressed"}}]
Please push the button on the Hue Bridge now (25 seconds left).
[{"success":{"username":"np***HQ"}}]
Hue API key: np***HQ
Room{name='Living'}:
- Light{id='1', name='Hue color lamp 1', type=EXTENDED_COLOR} was turned on
- Light{id='2', name='Hue color lamp 2', type=EXTENDED_COLOR} was turned off
- Light{id='3', name='Hue color lamp 3', type=EXTENDED_COLOR} was turned off

Working with Collections

So far we’ve lightly interacted with collections in the form of Lists. You might have also seen we also have arrays, which we have as a parameter for our main function: fun main(args: Array<String>)

You can think of collections as arrays on steroids. They provide ways of interacting with groups of items in many ways, from sorting to grouping, filtering, searching, etc.

The official docs state there are three types of collections in Kotlin:

  • Lists: Ordered collections with indices
  • Sets: Collections of unique elements
  • Maps (or dictionaries): Collections of key-values

Each of these has an immutable and mutable variant, so if you want a map which you can populate / interact with over time, you’ll want to use a MutableMap as opposed to a Map.

An important thing to note is that the default implementation of Map (LinkedHashMap) preserves the order of insertion. However, HashMap keeps no state of such order.

Let’s briefly look at the power of collections, take a look at this code:

class Person(val name: String, val age: Int)

val peopleById: MutableMap<Int, Person?> = mutableMapOf(
1001 to Person("John", 25),
1002 to Person("Rose", 23),
1003 to Person("Simon", 29),
1004 to Person("Caroline", 17),
1005 to null
)

val elegiblePeople = peopleById
.filterValues { it != null }
.filterValues { it!!.age >= 18 }
.map { it.value!!.name }
.sortedBy { it }

println(elegiblePeople)

In this example we create a map where the key is the ID of a person, and the value is either null, or an instance of Person (which has name and age).

We then want to get a sorted list of the names of people who are at least 18 years old. So we filter the map by values which are not null and have an age of 18 or more, then we .map it with a single value which produces a list of names (instead of a new map, as we provided no ID), and finally we sort this list of strings.

The output of this code will be: [John, Rose, Simon]

Working this way we can produce code that is very readable, however, due to the flexibility of functional programming we can also end up with chains of calls that are huge and very complex, so the moment you notice something a chain is becoming unmaintainable, think about extracting a part into a method with a suitable name that can provide the transformation you are looking for.

Let’s get back to our program:

We’ll transform our main function to first make a map of lights with actions to perform (turn on/off), then we’ll perform those actions. The purpose of this is to further illustrate what we can do with collections, we’ll be simplifying this code further on.

Replace the main function with the following code:

fun main(args: Array<String>) {
val factory = HueFactoryNoop("192.168.1.6", null, "MyHueApp")
factory.initializeHue()
println("Hue API key: ${factory.apiKey}")
val lightActions = sequence {
factory
.getRooms()
.forEach { room ->
yieldAll(
room
.getLights(factory)
.sortedByDescending { it.name }
.map { it to Random.nextBoolean() }
)
}
}
.toMap().toSortedMap(compareBy<String> { it.name })

for (lightAction in lightActions) {
val (light, turnOn) = lightAction
when {
turnOn -> {
factory.turnOn(light)
println("- ${light.name} on")
}
!turnOn -> {
factory.turnOff(light)
println("- ${light.name} off")
}
}
}
}

Let’s break down the first section: We are going to build a map where the keys will be the lights (objects or strings depending on whether you are using NOOP or noot 😉), and the values will be booleans, indicating the action to take (turn the light on or off).

The outer part is doing this:
sequence { ... }.toMap().toSortedMap(compareBy<String> { it.name })

First we create a sequence (introduced earlier on in the tutorial. If you need a refresher, a sequence code block allows us to “yield” results as needed). We convert this sequence of key-values to a map, which we then sort by name.

Inside the sequence block we have code very similar to what we had before, however the key difference is that for each room we get its light, sort them (descending, just to prove that our outer-most sorting of the map works), and we map these, setting the key to “it” which is the light object, and the value is a random boolean:

yieldAll(
room
.getLights(factory)
.sortedByDescending { it.name }
.map { it to Random.nextBoolean() }
)

The second block of code will iterate over the map and perform the actions. We start by iterating over it and destructuring the key-values:

for (lightAction in lightActions) {
val (light, turnOn) = lightAction

Destructuring allows us to set multiple values (in a single line) from either a map, a Pair, or something that returns an array/collection. In PHP the equivalent would look like this:

$array = [1, 2, 3, 4];
list($a, $b, $c) = $array;

Another way of writing our for loop would be to destructure in the signature:
for ((light, turnOn) in lightActions) {

Finally we have our when block, which is basically “switch…case” for Kotlin. When blocks are pretty flexible, there are multiple ways of using them (see the docs for more info), but the two ones you’ll see the most are these:

  • when (x) { 5 -> println(1); 6 -> println(2) }
  • when { x == 5 -> println(1); x > 5 -> println(2) }

Similar to CASE…WHEN clauses in SQL if you think about it.

Running the program should output something similar to this:

Hue API key: n****Q
- Hue color lamp 1 on
- Hue color lamp 2 off
- Hue color lamp 3 on
Process finished with exit code 0

We made our code way more complex for the purpose of illustrating a few concepts, but in our previous example above the code was perfectly acceptable #keepitsimple.

Let’s try to make the code simpler again by abstracting a bit and making the action handler more generic.

Data Classes, Enums and Constants

Add these lines at the top of your file, just below the other two exception classes you already created:

const val WHITE = "#FFFFFF"enum class LightAction {
ON, OFF, SWITCH_COLOR
}

data class LightWithAction(
val light: Light,
val action: LightAction,
val color: java.awt.Color? = java.awt.Color.decode(WHITE)
)
data class StringWithAction(
val light: String,
val action: LightAction,
val color: java.awt.Color? = java.awt.Color.decode(WHITE)
)

First we have a compile-time constant for the hex representation of white. Constants work pretty much the same way as in most other languages.

Then we have an enum class, a list of options to choose from. Enum classes however are more powerful than just a simple list. This quote from the previous link explains this better:

Enum constants today aren’t just mere collections of constants — they can have properties, implement interfaces, and much more

Finally we have a couple of data classes, which are like normal classes but simpler, designed for use as ‘value objects’, and must have a primary constructor with at least one property.

This article from the official docs provides more information on classes. We’ve now touched on the three main types: classes, enum classes, and data classes, as well as interfaces. For abstract classes simply prefix class with abstract, this article covers abstraction in detail.

The StringWithAction class in our example just supports our Noop use-case.

We’ll now add methods to the interface and our HueFactory classes that will allow us to perform an action (turn on/off a light, or switch its colour).

Add these to the HueFactoryInterface (below the last function):

fun performAction(item: LightWithAction) { }
fun performAction(item: StringWithAction) {
println("Performing '${item.action}' on ${item.light}")
}

This provides a default implementation for StringWithAction which means we don’t have to implement it in our HueFactoryNoop.

Let’s scroll down to our HueFactory and add our action processor at the end of the class:

override fun performAction(item: LightWithAction) {
when (item.action) {
LightAction.ON -> { this.turnOn(item.light) }
LightAction.OFF -> { this.turnOff(item.light) }
LightAction.SWITCH_COLOR -> {
if (item.color != null) {
this.switchColor(item.light, item.color)
}
}
}
}

Now we can simplify our main method. Replace it with the following block:

fun main(args: Array<String>) {
val factory = HueFactoryNoop("192.168.1.6", null, "MyHueApp")
factory.initializeHue()
factory
.getRooms()
.forEach { room ->
room
.getLights(factory)
.sortedBy { it.name }
.forEach {
val item = StringWithAction(
it,
if (Random.nextBoolean()) LightAction.ON else LightAction.OFF,
null
)
println("${it.name} is turning ${item.action}")
factory.performAction(item)
}
}
}

Note: If you want to use the real implementation (instead of NOOP) you will have to use LightWithAction instead of StringWithAction, we could get around this with more abstraction, but I think it’s redundant for the purpose of this tutorial.

Inside the forEach we create StringWithAction items, you’ll notice the second parameter is this code: if (Random.nextBoolean()) LightAction.ON else LightAction.OFF, these are inline conditionals (or ternary operators).

Now we get this output if we run the program:

Hue color lamp 1 is turning OFF
Hue color lamp 2 is turning ON
Hue color lamp 3 is turning OFF
Process finished with exit code 0

Perfect! Well, not quite, but we’ve learned a few things along the way.

Let’s say for some reason you want two lists, one of lights to turn on, and one of lights to turn off, and then you want to combine them into one list to perform the actions…

There are multiple ways of achieving this, so we’re about to slightly over-engineer things again just to illustrate a different path.

Mutable vs Immutable

Start by deleting everything in your main function after factory.initializeHue() so you are left with only the first two lines.

Just afterwards, add these two lines for out two mutable lists:

val lightsOn = mutableListOf<StringWithAction>()
val lightsOff = mutableListOf<StringWithAction>()

Just like listOf() allows us to initialise an immutable list of predetermined items, mutableListOf() lets us initialise one that can be changed (mutated). As we’ll be populating it in a forEach loop, this is necessary.

The type of items in the list is specified with <StringWithAction>, switch this for <LightWithAction> if you are going to test it with a Hue bridge.

In addition to mutable lists, you also have mutable sets, mutable maps, etc. You can find out more in the Kotlin collections documentation.

Now we can add this code below to populate our lists:

factory
.getRooms()
.forEach { room ->
room
.getLights(factory)
.sortedBy { it.name }
.forEach {
if (Random.nextBoolean()) {
lightsOn.add(StringWithAction(it, LightAction.ON, null))
} else {
lightsOff.add(StringWithAction(it, LightAction.OFF, null))
}
}
}

You’ll notice not much has changed except for adding items to each list with the add() method on the list. If you had a List as opposed to a MutableList this method would not be accessible.

Likewise, more methods are now available such as remove, removeAll, replace, replaceAt, amongst others.

Now let’s perform the actions by adding this code just below:

val items = listOf(*lightsOn.toTypedArray(), *lightsOff.toTypedArray());
items.forEach {
println("${it.light} is turning ${it.action}")
factory.performAction(it)
}

We combined both items into a new immutable list, and then looped over it and performed the actions.

You’ll notice we’ve added *, this allows you to destructure a full array (as opposed to using (item1, item2) which we used to destructure specific items), similar to how the spread operator works in JS. Read this article for more on destructuring / spreading varargs.

Another way is to use operators that work with lists, such as summing them:

val items = lightsOn + lightsOff

Simple enough!

What if you wanted to do this with a function instead?

You could try this:

val items = lightsOn.addAll(lightsOff)

However… you’ll see it has lost the type inference! Furthermore if you inspect addAll you’ll see it actually returns a boolean, specifying whether the original list was changed.

This is because it is mutating the original list, not returning the combination of the items.

One option would be to create a third mutable list and add the combination of both list into it, or we can use the union method of arrays instead:

val items = lightsOn.toTypedArray().union(lightsOff)

The point I’m trying to make is that we’re only scratching the surface of iterators & collections (lists, arrays, hash maps, etc.), there is a ton you can do, and the Kotlin Collections reference overview has what you need to dive in.

Scope Functions

Scope functions (See that link for the description of what scope functions are)

Replace the main function with the following code:

fun main(args: Array<String>) {
val factory = HueFactoryNoop("192.168.1.6", null, "MyHueApp")
factory.initializeHue()
val lightsOn = mutableListOf<StringWithAction>()
val lightsOff = mutableListOf<StringWithAction>()
with (factory.getRooms()) {
for (room in this) {
room.getLights(factory).let { lightsInRoom ->
lightsInRoom
.filter { Random.nextBoolean() }
.map { StringWithAction(it, LightAction.ON, null) }
.run {
lightsOn.addAll(this)
}
lightsOff.addAll(
lightsInRoom
.filter { !lightsOn.contains(
StringWithAction(it, LightAction.ON, null)
) }
.map { StringWithAction(it, LightAction.OFF, null) }
)
}
}
}
val items = lightsOn + lightsOff
items.forEach {
println("${it.light} is turning ${it.action}")
factory.performAction(it)
}
}

I’ve highlighted a few areas of interest in bold text.

The first scope function you see is with, notice “this”

Then we have let, notice “it”

Finally we have run, which allows us to run some code with the result of a chain, so in our example with the lights in the room we filter out some of them (where the result of this expression is a new collection consisting of some elements where the random boolean resulted in true), then we map these to a new collection with StringWithAction objects, finally on this collection we perform our run, which has access to the collection using this, which we use to add the items to the lightsOn list.

Compare it with the next block which we start with lightsOff.addAll(, you’ll see we need one more level of indentation in the second example, however it is more explicit regarding the intention of the block of code. This is not the best example of a use-case of run, the examples provided in official documentation are better (such as setting initial properties on a server).

However, hopefully you get the gist of when to use scope functions, and how sometimes they can lead to cleaner code (or less levels of indentation).

Companion Objects

Won’t cover (moved onto other projects)

Lambdas & Higher-Order Functions

Won’t cover (moved onto other projects)

However, I found this diagram cool:

https://kotlinlang.org/docs/reference/type-safe-builders.html

https://en.wikipedia.org/wiki/Closure_(computer_programming)

https://www.i-programmer.info/programming/other-languages/12255-the-programmers-guide-to-kotlin-anonymous-and-lambda-functions.html?start=2

https://en.wikipedia.org/wiki/Currying

File I/O

Won’t cover (moved onto other projects)

Resources & Settings

Won’t cover (moved onto other projects)

Coroutines

Purpose: To avoid blocking the main thread — e.g. “turn on/off multiple lights at the same time”

Add this to gradle build file implementation(“org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3”)

Add this to gradle build file implementation(“org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.3”)

I wish I could cover this as it is super exciting and one of the things that makes Kotlin totally worth it, but I won’t cover (moved onto other projects) — there are luckily a lot of resources available that cover coroutines in depth.

An interesting thing to note is exception handling for coroutines (what happens if an exception happened in a coroutine? how do you bubble it up?) there are multiple ways of solving this, I suggest you go into a deep dive into this topic as well as it demystifies some of the intricacies regarding coroutines.

Dependency Injection

Won’t cover (moved onto other projects)

Thanks for reading, Sorry I couldn’t cover everything I wanted to originally, but I hope you found it informative!

--

--

Mike Warner

Software Engineer specialised in PHP, Python, NodeJS, Kotlin and React