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
- General-purpose, statically-typed, open-source language
- Created by JetBrains – the company who built IntelliJ IDEA and Android Studio
- v1.0 released 2016
- It runs on a Java Virtual Machine (JVM) and can be used anywhere Java is used
- Somewhat similar to Java, but has many simplifications in syntax and a variety of modern capabilities, such as null safety
- Avoid boilerplate code
- Avoid specifying types when unnecessary
- Android mobile development has been “Kotlin-first” since 2019
- Over 95% of the top thousand Android apps use Kotlin.
- Used by over 60% of professional Android developers
- 100% Java interoperable
- Can call Java code from Kotlin and Kotlin code from Java
- https://kotlinlang.org/docs/java-interop.html
- https://kotlinlang.org/docs/java-to-kotlin-interop.html
Part of the reason to learn Kotlin is to take advanage of Jetpack Compose. (what is that?)
- Modern declarative UI toolkit for component-based UI (“composables”)
- Created by Google
- V1.0 released July 2021
- 100% Kotlin
- Greatly simplifies the coding of UI for Android
- Can cut development time in half
- Is now the recommended approach to creating UIs for Android
- Conceptually similar to React and Flutter
- Similar declarative approach using components
- Strong integration with Google’s Material Design
- 24% of the top 1000 apps on Google Play have adopted Compose
- Lots of great documentation to help you learn
- 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)
- Uses Gradle (build.gradle.kts) to specify dependencies on 3rd party packages
- 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).
- Here we see a
- Nested UI components
- 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.
- Use
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 tolet
in Javascript) - The
val
keyword is used to declare a constant (similar toconst
in Java)
var name = "Joe"val birthyear = 1985
name = "Bobson" // this is finebirthyear = 1984 // Compiler error!
var name = "Joe"val birthyear = 1985
name = "Bobson" // this is finebirthyear = 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:
var height = 55var name: String = "Joe"val birthyear: Int = 1985
height = "fifty-five" // error!name = 55 // error!
var height = 55var 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 intvalue2.toString() // doesn't matter what value2 is, this should always work
value1.toInt() // throws an error only if value1 cannot be converted to an intvalue2.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.
val temperature = 20val isHot = if temperature > 40 true else falseprintln(isHot) // result: False
val temperature = 20val isHot = if temperature > 40 true else falseprintln(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.
val isUnit = println("This is an expression") // "This is an expression" printed to consoleprintln(isUnit) // "kotlin.Unit" printed to console
val isUnit = println("This is an expression") // "This is an expression" printed to consoleprintln(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")}
public static void main() { System.out.println("Hello, World!");}
public static void main() { System.out.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)}
public static void greeting(String message) { System.out.println("Hello, World!");}
public static void greeting(String message) { System.out.println("Hello, World!");}
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;}
public static String augmentMessage(String message) { return "This is the message: " + message;}
public static String augmentMessage(String message) { 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.
fun printHello() { println("Hello")}
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:
fun printHello(): Unit { println("Hello")}
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:
fun drive(speed: String = "fast") { println("driving $speed")}
drive() // driving fastdrive("slow") // driving slowdrive(speed="as fast as i like") // driving as fast as i like
fun drive(speed: String = "fast") { println("driving $speed")}
drive() // driving fastdrive("slow") // driving slowdrive(speed="as fast as i like") // driving as fast as i like
Here’s another example:
fun greeting(name: String = "Unknown", age: Int = 10) { println ("Hello $name, you are now $age old")}
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.
fun tempToday(day: String, temp: Int) { println("Today is $day and it's $temp degrees")}tempToday("Monday", 22)tempToday("Monday") // error
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.
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 inreformat("Today is the present future of yesterday", false, ", ", false) // default args can be replaced
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 inreformat("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.
reformat("Yesterday is the shadow of today", divideByCamelHumps=false, wordSeparator=", ", normalizeCase=false)
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 definitionfun 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 definitionfun 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 definitionpublic static String greeting(String name, int age) { System.out.println(String.format("Hello %s, you are now %s old", name, age));}
// function call (position argument function call)greeting("Bobson", 123); // "Hello Bobson, you are now 123 old"
// function call (new named argument function call)greeting(name="Bobson", age=123); // Syntax error! This isn't a thing in Java.
// function definitionpublic static String greeting(String name, int age) { System.out.println(String.format("Hello %s, you are now %s old", name, age));}
// function call (position argument function call)greeting("Bobson", 123); // "Hello Bobson, you are now 123 old"
// function call (new named argument function call)greeting(name="Bobson", age=123); // Syntax error! This isn't a thing in Java.
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:
fun convertToFahrenheit(degree : Float) : Float { return (degree * 9 / 5) + 32}
fun convertToFahrenheit(degree : Float) = (degree * 9 / 5) + 32
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:
fun double(x: Int): Int { x * 2 }
fun double(x: Int): Int { x * 2 }
fun double(x: Int): Int = x * 2
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.
- https://play.kotlinlang.org/byExample/overview
- https://developer.android.com/teach##teach-a-class
- https://www.w3schools.com/KOTLIN/index.php
- https://kotlinlang.org/docs/home.html
- https://eecs441.eecs.umich.edu/
- https://www.slideshare.net/GoogleDevelopersLeba/android-development-with-kotlin-course
- Codelabs: