<< RETURN: Coding / Kotlin / Coding with Kotlin
Setting Up Environment
Getting started with coding in Kotlin is easy. You’ll need a tool to write and execute Kotlin code such as an Integrated Development Environment (IDE). There are many IDEs that one can use to type out Kotlin code. I’ll be using IntelliJ IDEA Community Edition as it is free and it so happens that it comes from JetBrains, the creators of Kotlin.
You can download IntelliJ IDEA with this link. Note that the Ultimate Edition has a 30-day trial period. You are free to choose whichever edition you want to use for coding.
After downloading, simply double click on the installer and follow the steps.
For installation options, feel free to check whichever you want. After successful installation you will also need to create a project before you can start coding.
For the rest of this section I’ll be running through some basic stuff that you’ll need to know or come across when using Kotlin.
Variables & Data Types
Variables
What are variables? From a high level view, variables are simply names that can be assigned values of different data types and they store the assigned value for future use.
fun main() {
// the value 1 is stored in a
val a = 1
/*
some code later...
*/
// Console output -> 1
println(a)
}
Note that the main()
function, used in most of the sample code blocks, is the entry point of all Kotlin programs. Only code called directly or indirectly from main()
will be executed. Kotlin code without the main()
function can only be used as a package or library.
Data Types
The stored data can be of different types. Programmers will use different data types depending on their needs. Some basic data types are Integer, Float, Boolean and String.
val a = 1 // Integer
val b = 2.0 // Double
val c = true // Boolean
val d = "Hello World" // String
In Kotlin, variables are usually declared using a val
keyword or the var
keyword. Using val
means that the particular variable cannot be reassigned, while var
allows for reassignment.
fun main() {
val a = 1
a = 2 // Error when compiling
println(a)
}
fun main() {
var a = 1
a = 2
println(a) // Console output -> 2
}
As seen above, a variable declared with the val
keyword cannot be reassigned. However, let’s take for example Arrays.
fun main() {
val myArray = mutableListOf<Int>()
myArray.add(0)
myArray.add(1)
for (item in myArray) {
println(item)
}
/*
=Console output=
0
1
*/
}
In the sample code above, I declared myArray
using the val
keyword but I am still able to add values (0 and 1) to it afterwards. This is because, for objects and more complex data types this isn’t considered reassignment. Below is a similar sample code, but with an added reassignment which causes a compile time error.
fun main() {
val myArray = mutableListOf<Int>()
myArray.add(0)
myArray.add(1)
for (item in myArray) {
println(item)
}
/*
=Console output=
0
1
*/
myArray = mutableListOf<Int>() // Compile Error
}
Static Typing
Kotlin is also statically typed. This means that every Kotlin variable needs to know its data type at compile time. If you want to declare a variable first and assign a value to it later on, you will need to explicitly declare its data type.
fun main() {
// val a -> Compile Time Error
val a: Int
val b = 2
a = b + 1
println(a) // Console output -> 3
}
As seen above, I wanted to initialize the variable a
later on. Thus, I had to declare it as type Int
first. Otherwise, I would have encountered an error during compilation.
However, you won’t need to worry about this if your variable is initialized immediately. This is because Kotlin provides type inference.
Type Inference
Kotlin Compilers will automatically know what data type a particular variable is, based on the type of data assigned to it. This frees up the programmer from having to explicitly declare the data type of every single variable.
fun main() {
val a = 1
var b: Int = 2
println(a+b) // Console output -> 3
}
As seen above, the variable a
has no explicit data type definition, whereas variable b
is defined explicitly as an Integer. Because of type inference, there is no need to explicitly define the data type of variable a
and yet it is possible to add it with another Integer variable b
.
This works not only for Integer variables, but for other data types as well.
Operators
We use operators to perform calculations and manipulate data.
Addition Operator +
fun main() {
var a = 1
val b = 2
a += 1 // a = a + 1
println(a+b) // Console output -> 4
}
For numerical data types, the +
operator adds those values together.
fun main() {
val a = "Hello"
val b = " World"
println(a+b) // Console output -> Hello World
val f = mutableListOf<Int>(1, 2)
val g = mutableListOf<Int>(3, 4)
f += g
for (item in f) {
println(item)
}
/*
=Console output=
1
2
3
4
*/
}
For strings and arrays, it concatenates them together.
Subtraction Operator -
fun main() {
var a = 1
val b = 2
a -= 1 // a = a - 1
println(a-b) // Console output -> -2
}
For numerical data types, the -
operator subtracts the left hand side value with the right hand side value.
fun main() {
val f = mutableListOf<Int>(1, 2, 3, 4, 2)
val g = mutableListOf<Int>(2)
f -= g.toSet()
for (item in f) {
println(item)
}
/*
=Console output=
1
3
4
*/
}
For arrays, f -= g
basically means to take out everything in array f
that exists in array g
. Thus, all the 2s were removed.
Multiplication Operator *
fun main() {
var a = 1
val b = 2
a *= 1 // a = a * 1
println(a*b) // Console output -> 2
}
For numerical data types, the *
operator multiply those values together.
Division Operator /
fun main() {
var a = 1
val b = 2
a /= 1 // a = a / 1
println(a/b) // Console output -> 0
}
For numerical data types, the /
operator divides the right hand side value from the left hand side value. Also, for integer division in Kotlin, the result will be rounded to the nearest integer.
fun main() {
var a = 1.0
val b = 2.0
a /= 1 // a = a / 1
println(a/b) // Console output -> 0.5
}
If you want to keep the accuracy of your calculation, consider switching to using Float/Double
.
Modulo Operator %
fun main() {
var a = 5
val b = 10
a %= 3 // a = a % 3
println(a%b) // Console output -> 2
}
For numerical data types, the %
operator divides the right hand side value from the left hand side value and gets the remainder.
Conditional Branches & Control Flow
Now we move on to Conditional Branches and Control Flows. This section is mostly intuitive, so I won’t be explaining too much. Basically, if the specified conditions are met, then the code within that section is executed.
If Else
fun main() {
val a = 4
if (a > 5) {
println("a > 5")
} else if (a < 0) {
println("a < 0")
} else {
println("0 < a < 5")
}
// Console output -> 0 < a < 5
}
In the above code, after failing the first two conditions, it goes into the else
block. Note that if either the first or second condition succeeds, subsequent blocks would not be executed.
While
fun main() {
var a = 0
do {
println("Happy")
a += 1
} while (a < 3)
while (a < 5) {
println("Birthday")
a += 1
}
/*
=Console output=
Happy
Happy
Happy
Birthday
Birthday
*/
}
Both the while
and do-while
loop will execute repeatedly until the specified condition fails. Their main difference being that do-while
will run first & check later, and while
will check first & run later.
When
fun main() {
val a = 2
when (2) {
1 -> println("when 1")
2,3,4 -> {
println("when 2, 3 or 4")
}
else -> {
println("when not 1-4")
}
}
// Console output -> when 2, 3 or 4
}
Similar to the if
statement, but when
is usually used when there are more conditions and the conditions are not complex (kind of like multiple choice).
For
fun main() {
for (i in 0..5) {
println(i)
}
/*
=Console output=
0
1
2
3
4
5
*/
for (i in 0..5 step 2) {
println(i)
}
/*
=Console output=
0
2
4
*/
for (i in 0 until 5) {
println(i)
}
/*
=Console output=
0
1
2
3
4
*/
val s = "Hello"
for (i in s.indices) {
println("[$i] = ${s[i]}")
}
/*
=Console output=
[0] = H
[1] = e
[2] = l
[3] = l
[4] = o
*/
for (i in s.indices step 2) {
println("[$i] = ${s[i]}")
}
/*
=Console output=
[0] = H
[2] = l
[4] = o
[6] = W
[8] = r
[10] = d
*/
for (c in s) {
println(c)
}
/*
=Console output=
H
e
l
l
o
*/
}
for
loops are really powerful. You can specify what values you want to iterate through as well as how big each increment will be. This is even more so when dealing with strings and arrays.
Functions
Another key component of any programming language are functions. They are extremely useful and most good coding practices (DRY) likely involve functions.
fun main() {
var a = 10
var b = 5
while (a > 0) {
println("Countdown: $a")
a -= 1
}
while (b > 0) {
println("Countdown: $b")
b -= 1
}
}
Take for example the above code, it will output 10 to 1 and then 5 to 1. But using functions, we can make the code simpler and also reusable in other parts of the code.
fun main() {
countDown(10)
countDown(5)
}
fun countDown(i: Int) {
var num = i
while (num > 0) {
println("Countdown: $i")
num -= 1
}
}
Now the main()
function is much cleaner and readable. Also, the function countDown(Int)
can now be called from other parts of the program to perform the countdown tasks.
fun main() {
sayGreetings()
val decrementNum = { i: Int ->
var num = i
while (num >= 0) {
println("Countdown: $num")
num -= 1
}
0 // Kotlin treats this as the return value
}
var count = 5
while (count >= 0) {
if (isPerfectMoment(count)) {
sayHappyBirthday()
break
} else {
count = decrementNum(count)
}
}
}
fun sayGreetings() {
println("Hello!")
}
fun sayHappyBirthday() {
println("Happy Birthday!")
}
fun isPerfectMoment(count: Int): Boolean {
return count == 0
}
In the above example, there are different kind of functions. Functions that perform some tasks but don’t return anything like sayGreetings()
and sayHappyBirthday()
, functions that take in an argument and return a value like isPerfectMoment(Int): Boolean
, and anonymous functions like decrementNum(Int): Int
that can only be used within the same scope.
This section simply aims to bring to your attention that there are different ways to declare and use functions, details will be discussed in other posts.
Objects & Classes
What are Classes and Objects? Classes are like blueprints, and Objects are the things created from that blueprint.
fun main() {
val human = Animal(2, 80.5, "Human")
val tiger = Animal(4, 105.2, "Tiger")
val whale = Animal(0, 130000.0, "Whale")
human.sayIntro()
tiger.sayIntro()
whale.sayIntro()
}
class Animal(private val legs: Int, private val weight: Double, private val name: String) {
private fun getLegs(): Int {
return legs
}
private fun getWeight(): Double {
return weight
}
private fun getName(): String {
return name
}
fun sayIntro() {
println("Hello, I am a ${getName()}. I weigh ${getWeight()} kg and I have ${getLegs()} legs.")
}
}
/*
=Console output=
Hello, I am a Human. I weigh 80.5 kg and I have 2 legs.
Hello, I am a Tiger. I weigh 105.2 kg and I have 4 legs.
Hello, I am a Whale. I weigh 130000.0 kg and I have 0 legs.
*/
As seen in the above sample code, with the Animal
class we can instantiate as many animal objects as we want. Being able to store and associate multiple values to a single variable is extremely powerful. This also makes code more maintainable, scalable, and modular.
Next Up
Once you’re done getting your feet wet with basic programming in Kotlin and IntelliJ IDEA, you can see how to get started with Compose Multiplatform