Lecture 18 Special Topics: Kotlin

This lecture introduces Kotlin, an open source programming language that runs on the Java Virtual Machine and is fully interoperable with Java Code. It is designed to be a more concise, more flexible, and faster alternative to Java while offering language-level support to features such as null-checking and functional programming techniques. As of May 2017, Google offers first-class support for using Kotlin when developing Android applications.

  • Kotlin was developed by JetBrains, the creators of the IntelliJ IDE which forms the basis for Android Studio.

This lecture references code found at https://github.com/info448-s17/lecture16-kotlin. Kotlin demo code can be found in the kotlin folder; build and run this code through gradle with the command gradle -q.

  • Note that the provided gradle build script includes the Kotlin standard library for Java 8, making (simplied) versions of Java classes available.

18.1 Kotlin Syntax

The Kotlin language draws many syntactical structures from popular modern languages, including JavaScript and Swift (used for iOS development); I find it has a lot in common with TypeScript.

The full documentation for the Kotlin language can be found at https://kotlinlang.org/docs/reference/; practice and examples are available in the form of Kotlin Koans. This lecture does not aim to be a complete reference but rather to highlight some of the most noticable or interesting aspects of the langauge.

18.1.1 Variables

Kotlin variables are declared by using the keyword var (for mutable variables) or val (for read-only variables); this is similar to let and const in JavaScript ES5. Like Java, Kotlin is a statically-typed language—the variable type is written after the variablen name, separated by a colon (:). For example:

val x:Int = 448  //read-only
var stepCount:Int = 9000  //mutable
stepCount++  //can change the variable
val message:String = "Hello" + " " + "world"  //supports normal operators

Notice that each of these statements lacks semicolons! Similar to many scripting languages, statements can separated either by a colon or by a newline.

In Kotlin, primitive types (e.g., int, double, boolean) and implemented using classes, allowing for all values to support instance methods and variables (called properties). These classes are named after their Java equivalents (though of course start with a capital letter, since they are classes!)

  • The compiler optimizes the types to avoid extraneous overhead.

Additionally, variable type can often be inferred from the assigned value, so may be omitted:

var x = 448  //`Int` type is inferred
x-40  //valid mathematical operation
  • Note even without the explicit type declaration, the variable is strongly typed—it’s just that the variable type is determined by the compiler. I encourage you to include the explicit type declaration, particularly when it helps clarify the semantic meaning of the variable.

One of Kotlin’s major features is null safety, or the ability to catch null-pointer errors at compile time. In Kotlin, we need to explicitly declare that a variable can be assigned null as a value. We do this by including a question mark ? immediately after the type declaration:

var message:String = "Hello"
message = null  //compilation error!

var possiblyNull:String? = "Hello"
possiblyNull = null  //no problems!
  • Kotlin provides a number of other structures using the ? to check for and handle null values; see Null Safety for details.

Basic Kotlin types work pretty much the same as basic Java types. A few notable differences and features:

  • Warning: Because of the classes Kotlin uses for numeric types, it is unable to automatically convert to a “larger” type (e.g., from Int to Double). This casting needs to occur explicitly:

    val integer:Int = 5
    var root:Double = Math.sqrt(integer)  //compile type error!
                                          //sqrt() takes in a double
    root = Math.sqrt(integer.toDouble())  //no problems!
  • Fun feature: in Kotlin, numeric literals can be written with underscores (_) as readability separators; the underscores are removed during compile time.

    val oneMillion:Int = 1_000_000
  • Fun feature: Kotlin also supports string templating, in which variables used within the String are automatically evaluated (avoiding the need for complex String concatenation). Variables to evaluate are prepended with a dollar sign $, while more complex expressions can be evaluated from inside ${}

    val quantity = 5
    val price = 2.95
    val cost:String = "$quantity items at $price costs ${quantity*price}"
  • Note: Arrays in Kotlin are represented by the Array<T> class. The Array<T> class has a private constructor though; in order to create an array, use the arrayOf() factory method (to create an array out of the given parameters), or arrayOfNulls() to create an array of n null elements. Note that arrays still support using bracket notation ([]) for accessing values.

    var numbers:Array<Int> = arrayOf(1,2,3,4,5)  //create an array
    println(numbers.size)  //kotline uses `size` property for array length
    println(numbers[3])  //4
    numbers[4] = 10  //assign value as normal
    • Kotlin also provides specialized classes for arrays of basic types, e.g., IntArray

18.1.2 Functions

Kotlin functions are in some ways closer to JavaScript functions that to Java methods. For one, Kotlin supports package-level functions that do not exist as members of a particular class, as an alternative to static functions in Java. Thus we can talk about Kotlin functions independent of classes (which are discussed below).

Functions in Kotlin are declared with the fun keyword (not because they are fun, though they can be). This is folowed by the name of the function and the parameter list (where the parameters are given explicit types). The return type is declared after the parameter list, following a colon (:). For example:

fun makeFullName(first:String, last:String): String {
    val full = first + " " + last
    reurn full
}

val fullName = makeFullName("Ada", "Jones")

Java’s void return type is in Kotlin represented by the singular value Unit. If a function returns Unit, it can and should be omitted:

fun sayHello() { //returns Unit, but type omitted
    println("Hello world");
}

Kotlin functions also support named default argments: you can provide a default value for an argument in the function declaration. If that (positional) argument is omitted, then the default value is used instead. Additionally, arguments can be specified by name when calling the function, allowing you to include them out of order.

fun greet(personTo:String = "world", personFrom:String = "Me") {
  println("Hello $personTo, from $personFrom")
}

greet()  //use defaults for arguments
greet("y'all")  //use default for second argument
greet(personFrom="Myself", personTo="You")  //named arguments (out of order)

Similar to JavaScript arrow functions, Kotlin functions whose body is just a single expression can leave off the block entirely, instead writing the expression to return after an equals sign (=)—as though that expression were assigned to the function:

//a function square() that takes in an Int and returns that number squared
fun square(n:Int):Int = n*n
  • Note that you can omit the return type from single expression functions, as it can be inferred.

This provides a very concise way of writing simple functions!

Similar to JavaScript (and drawing from Java 8), Kotlin supports higher order anonymous functions and lambda functions. These are functions that can be assigned to variables (including function parameter), just like any other object! These are highly effective when using function programming paradigms (e.g., map() and reudce()), or when specifying event callback functions.

Anonymous functions are normal functions (whether with a block body or just a single expression), but written without a name. These functions can be assigned to variables, or passed directly to functions that take in appropriate callbacks:

val square = fun(n:Int):Int {
    return n*n
}

val numbers:IntArray = intArrayOf(1,2,3,4,5)
println(numbers.contentToString())  //[1, 2, 3, 4, 5]

val squares:List<Int> = numbers.map(square)  //transform each element with square()
println(squares.joinToString())  //1, 4, 9, 16, 25
  • Note that the square variable here has an inferred type that is a function which takes in an Int and returns an Int. We can explicitly declare this type using the following syntax:

    val square:(Int) -> Int

    The argument types are listed in parentheses, followed by an arrow ->, followed by the return type.

Anonymous functions that are a single expression can also be written as lambda expressions. Lambda expressions are placed inside curly braces {}, with the parameter list coming first (not in parentheses). This is followed by a arrow ->, followed by the expression that should be evaluated.

val add = { x:Int, y:Int -> x+y }

//with an explicit typed (so inferred in the expression)
val add:(Int, Int) -> Int
add = { x,y -> x+y }

In fact, is Kotlin is able to figure out the signature for a lambda function with only a single parameter (e.g., because the lambda is being anonymous passed in as a defined callback function, so must meet the required interface), it will allow us to omit the signature entirely. In this case, the single parameter is automatically assigned to the variable it:

val numbers:IntArray = intArrayOf(3,1,4,2,5)

//filter() takes a callback with a single parameter that returns boolean,
//so the signature can be inferred
val filtered:List<Int> = numbers.filter( {it > 2} )

println(filtered.joinToString())  //3, 4, 5

This allows us to write somewhat readable code using functional programming approaches. As an example from the documentation:

fruits
  .filter { it.startsWith("a") }
  .sortedBy { it }
  .map { it.toUpperCase() }
  .forEach { println(it) }

18.1.3 Classes

Kotlin is an object-oriented language, just like Java. Thus despite being able to support package-level functions, Kotlin is primarily written using classes with attributes and methods.

As in Java, Kotlin classes are declared using the class keyword. Additionally, most Kotlin classes have a primary constructor, the parameters of which can be included as part of the class declaration.

//declares a class `Dog` whose constructor takes two parameters
class Dog(name:String, breed:String) {
    var name = name   //assign to properties
    val breed = breed
}
  • Of course constructor parameters can support default named arguments as well!
  • Kotlin properties (instance variables) default to being public, but can be declared private if desired. See Visibility Modifiers.
  • Note that methods in Kotlin are simply functions (written as above) declared inside the class body.

The primary constructor parameters can directly declare the variables by including either var or val as appropriate. Thus a shortcut for the above constructor and initialization is:

//declares instance variables
class Dog(var name:String, val breed:String) {

}
  • Additional constructors are specified using the constructor keyword as the method name. See the documentation for details.

Objects in Kotlin are instantiated by simply calling the constructor as a normal function, without the new keyword.

val myDog = Dog("Fido","mutt")  //no new keyword!
myDog.name  //"Fido"
myDog.breed  //"mutt"

As with any other object-oriented language, Kotlin also supports inheritance, with all classes existing within an inheritance hierarchy. Note that in Kotlin, all classes by default inherit from the class Any, rather than from java.lang.Object.

We can specify a classes inheritance by putting the parent type after a colon (:) in the declaration. If the child class has a primary constructor, then we also include the parameters which should be passed to the parent’s constructor.

class Dog(var name:String, val breed:String) : Animal() {

}
  • Interfaces are implemented the same way, as part of a comma-separated list of inherited classes. That is, there is no distinction between extending a class and implementing an interface.

Importantly, Kotlin requires us to explicitly mark classes as “subclassable” (they are otherwise compiled to final classes). We do this by using the open keyword as an annotation in the class declaration:

//declare class as open
open class Animal(var name:String) { }

//constructor parameters can be passed to parent constructor
class Dog(var name:String) : Animal(name) { }

The open keyword is also needed if we want to allow a method to be overridden. Additionally, any methods that override a parent version need to be annotated as such with the override keyword.

open class Animal() {
    //function can be overridden
    open fun speak() {
      println("The animal speaks")
    }
}

class Dog() : Animal() {
    //overrides parent function
    override fun speak() {
      println("The dog barks")
    }

    //overriding toString() using an expression function
    override fun toString() = "A dog"
}

Note that Kotlin does not have static class methods; instead, just use package-elevel functions!

Kotlin also supports a few other special kinds of classes:

  • Data Classes are declared using the data keyword as an annotation in the class declaration. These are classes that do nothing but hold data: they have only attributes, no methods (similar to a struct in C). The compiler will automatically provide implementations of toString() and equals() for these classes.

    data class Ball(var x:Int, var y:Int, val color:String)
    
    val myBall = Ball(30,30,"red")
    print(myBall)  //Ball(x=30, y=30, color=red)
  • Nested classes can be made into inner classes (e.g., “non-static” classes with access to the containing class’s instance variables) using the inner annotation in the class declaration.

  • Anonymous classes (called Object expressions) can be created with the object keyword in place of a class and the class name:

    button.addActionListener(object : ActionListener() {
        //class definition (including methods to override) goes in here!
          override fun actionPerformed(ActionEvent event) {
                //...
          }
    });

This is pretty similar to Java, but using object instead of new to instantiate the anonymous class.

18.2 Kotlin for Android

Kotlin is built into Android Studio 3.0 by default, but is also available via a plugin for earlier versions. You can install this plugins through the Preferences > Plugins menu: click the Install JetBrains plugin... button at the bottom and select Kotlin. Note that you will need to restart Android Studio.

After you’ve installed the plugin, you can use Android Studio to convert an existing Java file to a Kotlin source code file (.kt) by selecting Code > Convert Java File to Kotlin File from the menu. Once you start editing the file, you will be prompted to configure the project to use Kotlin. This will modify the Gradle build scripts to include Kotlin support, allowing you to build and run your app as usual.

For example, we can convert the provided MainActivity, and consider the changes that have been made:

  • The adapter instance variable can be null, so its type includes a ?.
  • Notice how Intents are addressed, particularly the ::class.java syntax.
  • For loops use for ... in syntax, and Kotlin supports ranges.
  • You can fix the syntax error with the adapter by explicitly casting the ListView as a ListView (this is an example of the converting misunderstanding the code; it happens!)
  • Static variables are wrapped in a companion object.

This section of the lecture is incomplete.

18.2.1 The Android Extension

https://kotlinlang.org/docs/tutorials/android-plugin.html

To include:

apply plugin: 'kotlin-android-extensions'

To avoid findViewById()

import kotlinx.android.synthetic.main.<layout>.*

18.2.2 Anko

https://github.com/Kotlin/anko

dependencies {
    compile "org.jetbrains.anko:anko-commons:$anko_version"
}

Logging: https://github.com/Kotlin/anko/wiki/Anko-Commons-%E2%80%93-Logging

  • Avoids the explicit call to Log, and uses the class name as the TAG. There are other Java libraries that support easier logging as well.
info("London is the capital of Great Britain")
debug(5) // .toString() method will be executed
warn(null) // "null" will be printed

Intents: https://github.com/Kotlin/anko/wiki/Anko-Commons-%E2%80%93-Intents

startActivity(intentFor<SomeOtherActivity>("id" to 5).singleTop())
//or
startActivity<SomeOtherActivity>("id" to 5)

Toast and Alerts: https://github.com/Kotlin/anko/wiki/Anko-Commons-%E2%80%93-Dialogs

//toasts
toast("Hi there!")
longToast("Wow, such a duration")

//alert
alert("Hi, I'm Roy", "Have you tried turning it off and on again?") {
    yesButton { toast("Oh…") }
    noButton {}
}.show()