Skip to content
Application Development II
GitLabGitHub

Introduction to Kotlin

Fundamentals of the main programming language we will use this semester.
Overview

These notes are adapted from slides by Talib Hussain. There may be some mistakes — let me know if there is something here that doesn’t make sense.

Fundamentals of Kotlin

  • Functions
  • Variables
  • Types
  • Strings
  • Arrays
  • Lists

Kotlin Operations

  • Loops
  • Ranges
  • When

Functional Programming

  • Lambda functions
  • .map, .filter

Object Oriented Programming

  • Classes
  • Constructors
  • Inheritance

Part of the reason to learn Kotlin is to take advanage of Jetpack Compose. (what is that?)

  • Open Android Studio and follow the default setup steps for a new project.
  • The default project (“Empty Activity”) in Android Studio actually creates a Kotlin-Compose app
    • It is now the standard for Android app development
  • There is a very cool new feature called “Live Edit”, but it is only available in the very latest release of Android Studio and Gradle.
    • You may need to update (try doing that!)
  • We’ll walkthrough the project files to see what the basic setup is
    • Uses Gradle (build.gradle.kts) to specify dependencies on 3rd party packages
      • Analogous in some ways to using npm for Node.js, but all specified explicitly in a single place. Gradle will download all required packages for you automatically.
    • Gradle also specifies various details for building the project (e.g., applicationId, minSdk level, version)
  • Main entry point is “MainActivity.kt”
    • Single activity design

  • Notice the rough similarity to React JSX
    • Nested UI components
      • @Composable indicates a UI component much like a JSX component
    • Named “properties” on components
      • Really are named parameters to functions in Kotlin
    • “Single Activity” design pattern
      • Similar in rough concept to single page app design of React
    • State management and re-render UI when state changes
      • Here we see a ViewModel class approach. We’ll also see parameter passing, the provider pattern, and storing state in variables (more similar to React).
  • https://www.jetpackcompose.app/compare-declarative-frameworks/JetpackCompose-vs-React-vs-SwiftUI

What new features does Kotlin bring to the table that Java/any-other-programming language lacks?

  • What normally happens when you try to reference an object that hasn’t been created yet in your code? What can the consequences of that be?
  • What if the programming language we use just doesn’t let you create null objects?
  • The type system in Kotlin is designed around Null safety — that is, preventing the non-existence of data by design.

  • What is a user interface? Not just in computing — what other devices do you use on a regular basis?
  • Is each part of the interface entirely separate? Can the interface be understood as a collection of components, or just as one universal whole?
  • The answers to these questions have programming consequences! When does the DRY principle (do not repeat yourself) contradict the requirements of the interface? What is the least annoying way to repeat yourself when needed?
  • Composable design is emphasised in Kotlin: a complex user interface should be de-composable into constituent parts, and re-composable from previous solutions.

  • Do you care whether the buttons in Moodle/Omnivox/whatever website are written in XML, HTML, JSON, ???
  • When reading code, do you prefer to know how something is done, or what it does? Maybe there isn’t just one answer to this question, but consider the most common scenario: you are tasked with making changes to a codebase written by others, that you are not familiar with, and you don’t have very much time.
  • Kotlin emphasises Declarative design principles to prevent developers from having to reinvent the wheel each time a new component is added. Furthermore, declarative programming has the benefit of being much easier to read (interpret) than procedural programming.

  • Comments // or /* */
  • Operators: + - * / % ++ -- = += -= *= /= %= == != > < >= <= && || !
  • if / if-else / if-else-if
  • while / do-while
  • break / continue / return
  • Index arrays/strings starting at 0
  • Array API (access, append, delete, etc.)

  • Semicolons are optional
  • Declaring functions, parameters, return types
  • Declaring variables / constants
    • Specifying types is optional
  • No primitive types - All types are objects (hence capitalized)
  • Easy string interpolation using $
  • How to create an array, list, etc.
    • Use arrayOf() listOf() arrayListOf() etc.

One of the major differences from Java mentioned above is that Kotlin does not have primitive types — all types are defined objects. This causes Kotlin to have variable behavior that looks more like Javascript than Java, which is a handy way to remember the following:

  • The var keyword is used to declare a variable that may change in value (similar to let in Javascript)
  • The val keyword is used to declare a constant (similar to const in Java)
Kotlin
var name = "Joe"
val birthyear = 1985
name = "Bobson" // this is fine
birthyear = 1984 // Compiler error!
Kotlin
var name = "Joe"
val birthyear = 1985
name = "Bobson" // this is fine
birthyear = 1984 // Compiler error!
  • Use of val is recommended where possible.
  • Cool feature: we don’t have to specify a type – Kotlin will figure it out based on the first assignment.
    • Once a type is assigned, it can’t be changed.
  • But, we can still specify a type if we wish. Unlike Java, the type is written after the variable name and preceded by a colon
  • Let’s see another example:
Kotlin
var height = 55
var name: String = "Joe"
val birthyear: Int = 1985
height = "fifty-five" // error!
name = 55 // error!
Kotlin
var height = 55
var name: String = "Joe"
val birthyear: Int = 1985
height = "fifty-five" // error!
name = 55 // error!

  • Main types are:
    • Int
    • Double
    • Char
    • Boolean
    • String
    • Also: Float, Long, Short
    • Note: All capitalized (no primitive types!)
  • Conversion: every type class has built-in conversion methods
    • You can convert one type to another using a toXXX() method on the object
value1.toInt() // throws an error only if value1 cannot be converted to an int
value2.toString() // doesn't matter what value2 is, this should always work
value1.toInt() // throws an error only if value1 cannot be converted to an int
value2.toString() // doesn't matter what value2 is, this should always work
  • All of the familiar Java built-ins are in Kotlin as well: Double.MIN_VALUE, Integer.MAX_VALUE, etc.

In programming languages you are more familiar with (Java, C#, even Javascript), there is a distinction between expressions and statements, that is:

  • expression: code that is a value
  • statement: code that is not a value

Some examples in Java (pretty much the same in C#):

const int price = 300;
const int price = 300;

In Kotlin, almost everything is an expression and therefore has a value.

Kotlin
val temperature = 20
val isHot = if temperature > 40 true else false
println(isHot) // result: False
Kotlin
val temperature = 20
val isHot = if temperature > 40 true else false
println(isHot) // result: False

There is one notable exception: loops. There’s no sensible value for for loops or while loops, so they do not have values. If you try to assign a loop’s value to something, the compiler gives an error.

Kotlin
val isUnit = println("This is an expression") // "This is an expression" printed to console
println(isUnit) // "kotlin.Unit" printed to console
Kotlin
val isUnit = println("This is an expression") // "This is an expression" printed to console
println(isUnit) // "kotlin.Unit" printed to console

The first println() prints the string “This is an expression”; the second println() prints the value of the first println() statement.

Some other languages have statements, which are lines of code that don’t have a value. In Kotlin, almost everything is an expression and has a value, even if that value is kotlin.Unit (Kotlin’s Unit is equivalent to Java’s void.)

The fun keyword is used to declare a function.

fun main() {
println("Hello World")
}
fun main() {
println("Hello World")
}

Semicolons are optional! But, this means that line breaks are significant.

When a function has parameters, you must specify the type. Unlike in Java, the type is written after the parameter name, and is preceded by a colon:

fun greeting(message: String) {
println(message)
}
fun greeting(message: String) {
println(message)
}

If a function returns something, you must specify the return type.

  • The return type is written after the parentheses containing the parameters and preceded by a colon (and is before the brace)
  • If a function returns nothing, you can omit the type (i.e., no void!)
fun augmentMessage(message: String) : String {
return "This is the message: " + message;
}
fun augmentMessage(message: String) : String {
return "This is the message: " + message;
}

A function is a discrete block of code that performs an operation, and can return a value. In Kotlin, functions are declared using the fun keyword, and can take arguments with either named or default values. A function that is associated with a particular class is called a method.

  • A block of code that performs a specific task
  • Breaks a large program into smaller modular chunks
  • Declared using the fun keyword
  • Can take arguments with either named or default values

You define functions using the fun keyword, followed by the name of the function. As with other programming languages, the parentheses () are for function arguments, if any. Curly braces {} frame the code for the function.

Kotlin
fun printHello() {
println("Hello")
}
Kotlin
fun printHello() {
println("Hello")
}

If a function does not return any useful value, its return type is Unit. The Unit return type declaration is optional. This code fragement is equivalent to the one immediately preceeding it:

Kotlin
fun printHello(): Unit {
println("Hello")
}
Kotlin
fun printHello(): Unit {
println("Hello")
}

Functions may have:

  • Default parameters
  • Required parameters
  • Named arguments

Default values provide a fallback if no parameter value is passed. Use = after the parameter type to define default values:

Kotlin
fun drive(speed: String = "fast") {
println("driving $speed")
}
drive() // driving fast
drive("slow") // driving slow
drive(speed="as fast as i like") // driving as fast as i like
Kotlin
fun drive(speed: String = "fast") {
println("driving $speed")
}
drive() // driving fast
drive("slow") // driving slow
drive(speed="as fast as i like") // driving as fast as i like

Here’s another example:

Kotlin
fun greeting(name: String = "Unknown", age: Int = 10) {
println ("Hello $name, you are now $age old")
}
Kotlin
fun greeting(name: String = "Unknown", age: Int = 10) {
println ("Hello $name, you are now $age old")
}

Some, all or none of the parameters may have default values.

When calling a function, a parameter with a default argument may be omitted. In that case, the default value will be used.

As long as the omitted arguments are last in the parameter list, you can use positional arguments as well. The following are all valid:

  • greeting (age=5)
  • greeting(name="Joe")
  • greeting("Jane")

These are not valid:

  • greeting(5)
  • greeting(age=5, "Juan")

Why aren’t these valid? Positional arguments cannot be ambiguous — it needs to be clear which parameter each positional argument is being assigned to. Don’t worry about memorizing this — it should make logical sense.

If no default is specified for a parameter, the corresponding argument is required.

Kotlin
fun tempToday(day: String, temp: Int) {
println("Today is $day and it's $temp degrees")
}
tempToday("Monday", 22)
tempToday("Monday") // error
Kotlin
fun tempToday(day: String, temp: Int) {
println("Today is $day and it's $temp degrees")
}
tempToday("Monday", 22)
tempToday("Monday") // error

The tempToday() function takes two parameters, day and temp, both of which are required.

Functions can have a mix of default and required parameters.

Kotlin
fun reformat(str: String,
divideByCamelHumps: Boolean,
wordSeparator: Char,
normalizeCase: Boolean = true) {
// code body goes here
}
reformat("Today is the shadow of tomorrow", false, ", ") // required args passed in
reformat("Today is the present future of yesterday", false, ", ", false) // default args can be replaced
Kotlin
fun reformat(str: String,
divideByCamelHumps: Boolean,
wordSeparator: Char,
normalizeCase: Boolean = true) {
// code body goes here
}
reformat("Today is the shadow of tomorrow", false, ", ") // required args passed in
reformat("Today is the present future of yesterday", false, ", ", false) // default args can be replaced

One neat feature of Kotlin (that requires a bunch of extra code to replicate in Java) is that functions can be called using named arguments.

Arguments can be passed to a function with their name specified explicitly. Using named arguments is convenient when a function has a large number of parameters — you no longer have to rely on remembering the positional order of parameters when using argument names.

Kotlin
reformat("Yesterday is the shadow of today",
divideByCamelHumps=false,
wordSeparator=", ",
normalizeCase=false)
Kotlin
reformat("Yesterday is the shadow of today",
divideByCamelHumps=false,
wordSeparator=", ",
normalizeCase=false)

Normally, functions depend on being provided arguments in the order they are defined. This makes a function with many arguments difficult to interpret: imagine a function called createShape with four size parameters. When calling the function, that would look like createShape(10, 20, 0.3, 123) — what does each parameter mean? What if you get the order wrong?

Named arguments allow the function caller to explicitly specify the parameter for each argument in a function call. Here are some other examples:

// function definition
fun greeting(name: String, age: Int) {
println ("Hello $name, you are now $age old")
}
// function call (normal position argument)
greeting("Bobson", 123) // "Hello Bobson, you are now 123 old"
// function call (new named argument -- look at how clear that is!)
greeting(name="Bobson", age=123) // "Hello Bobson, you are now 123 old"
// function call (we can even change the order. Why not?)
greeting(age=123, name="Bobson") // "Hello Bobson, you are now 123 old"
// function definition
fun greeting(name: String, age: Int) {
println ("Hello $name, you are now $age old")
}
// function call (normal position argument)
greeting("Bobson", 123) // "Hello Bobson, you are now 123 old"
// function call (new named argument -- look at how clear that is!)
greeting(name="Bobson", age=123) // "Hello Bobson, you are now 123 old"
// function call (we can even change the order. Why not?)
greeting(age=123, name="Bobson") // "Hello Bobson, you are now 123 old"

Notice how much easier string interpolation is in Kotlin – cool.

Just like in Java, for a function that has a single expression, the curly braces are optional. The following code snippets are equivalent:

Kotlin
fun convertToFahrenheit(degree : Float) : Float {
return (degree * 9 / 5) + 32
}
fun convertToFahrenheit(degree : Float) = (degree * 9 / 5) + 32
Kotlin
fun convertToFahrenheit(degree : Float) : Float {
return (degree * 9 / 5) + 32
}
fun convertToFahrenheit(degree : Float) = (degree * 9 / 5) + 32

Here’s another example, compared with Java:

Kotlin
fun double(x: Int): Int {
x * 2
}
Kotlin
fun double(x: Int): Int {
x * 2
}

When a function returns a single expression, the curly braces can be omitted and the body is specified after a ”=” symbol.