Book Image

Hands-On Object-Oriented Programming with Kotlin

By : Abid Khan, Igor Kucherenko
Book Image

Hands-On Object-Oriented Programming with Kotlin

By: Abid Khan, Igor Kucherenko

Overview of this book

Kotlin is an object-oriented programming language. The book is based on the latest version of Kotlin. The book provides you with a thorough understanding of programming concepts, object-oriented programming techniques, and design patterns. It includes numerous examples, explanation of concepts and keynotes. Where possible, examples and programming exercises are included. The main purpose of the book is to provide a comprehensive coverage of Kotlin features such as classes, data classes, and inheritance. It also provides a good understanding of design pattern and how Kotlin syntax works with object-oriented techniques. You will also gain familiarity with syntax in this book by writing labeled for loop and when as an expression. An introduction to the advanced concepts such as sealed classes and package level functions and coroutines is provided and we will also learn how these concepts can make the software development easy. Supported libraries for serialization, regular expression and testing are also covered in this book. By the end of the book, you would have learnt building robust and maintainable software with object oriented design patterns in Kotlin.
Table of Contents (14 chapters)

What is a function?

As a program grows, complexity grows. If the code cannot handle this growth, it is easy to become bogged down in the complexity of application. The best way to manage our code is to break it down into small, self-contained portions and the complex problem can be solved by putting these portions together. Kotlin can help us to divide our code into small chunks, and we can then assign a meaningful name to our code. That block can perform one particular task for us. In different programming languages, this technique is called a method, subroutine, or procedure. In Kotlin, this technique is called a function.

There are several reasons for dividing code into functions:

  • Divide and conquer: A programmer can solve a complex task by dividing it into small functions.
  • Reusability: Pasting similar code in different places is not a good approach. In the future, if a program's logic changes, we must update the pasted code everywhere else. Functions help us to reuse code anywhere in our program, and if the function code changes, it will have an effect in all areas.
  • Debugging: With big, complex problems, if the code does not work as expected, it is often difficult to find the hidden bug in spaghetti code. Without well-defined functions, it is a difficult, frustrating, and time-consuming task to fix the problem. If everything is divided into functions, a coder can test each function one by one in order to confirm its output.
  • Abstraction: In order to use a function, it is enough to know its name and parameters. The programmer does not need to know how it is implemented and what logic is used by another programmer.

Function declaration

Kotlin is a fun programming language, and so the function name begins with the word fun. The fun statement is followed by the function name with parentheses ( ). The code block is defined after the parenthesis within the curly brackets as { code block }. Once we have finished writing our code block, we can call this function from anywhere by using nameOfTheFunction. Writing hello function displays a greeting message on the screen. See the following example:

fun hello(){
println("Hello from Kotlin")
}
fun main(args: Array<String>) {
hello()
}

A simple Hello from Kotlin message will now display on the screen.

Functions with no parameter and no return type

This is the simplest form of a function. See the following example:

 fun sayHello(){
println("Hello from Kotlin")
}

If a function does not return a value, the Unit keyword can be declared right after the function name.

Unit corresponds to the void type in Java.

The Unit keyword is optional. If no keyword is mentioned, Kotlin will consider Unit as a default value:

fun sayHello() : Unit{
println("Hello from Kotlin")
}

Function with parameters

Functions can receive one or more parameters as arguments:

fun hello(message : String) : Unit {
println("Hello from $message")
}

fun main(args: Array<String>) {
hello("Kotlin")
}

The hello function takes a string variable as a parameter. When the hello function is called from main, a string value is passed to that function.

If the function takes more than one parameter, all parameters are separated with a comma (,). Here is another example of writing a function that takes two parameters:

fun add(a : Int, b : Int) {
println("Result of $a + $b is ${a+b}")
}

fun
main (args: Array<String>){
add(4,5)
}

In a function declaration, variables cannot be declared with the val or var keywords, and the data type must be specified explicitly.

Functions with parameters and return types

Functions can receive parameters and return values as a result. See the following example, which takes a value as a parameter and returns the result:

fun myFun(message : String) : String {
return "Hello from $message"
}

fun main (args: Array<String>){
val result = myFun("Author")
println(result)
}

This function takes a string as a parameter and returns a string value. Like other programming languages, Kotlin also uses the return keyword to return a value from a function. The return value must be the same as the return type defined in the function signature:

fun add(i: Int, j: Int): Int

If a return type of the function is integer, the return statement must be an integer, otherwise the compiler will throw an error. Here is an example of a function called addValues. This takes two integer parameters, adds them, and returns an integer as a result:

fun addValues(i: Int, j: Int): Int{
val k = i + j
return k
}

fun main (args: Array<String>) {
val result = addValues(5,6)
println(result)
}

Function as an expression

In Kotlin, a function can behave as an expression. Let's take the example of the add function from the previous section and convert it into an expression:

fun addValues(i: Int, j: Int): Int {
return i + j
}

This function takes two parameters, adds them, and returns an integer value. When the function contains only one line of code, it can be written as an expression.

Create a new function as an expression:

fun addValuesEx(a : Int, b : Int) : Int = a + b

The explicit declaration of the function return type can be removed as follows:

fun addValuesEx(a : Int, b : Int) = a + b

Now add an equals operator, remove the curly brackets, and remove the explicit type declaration of the return type along with the return keyword from the function body. The compiler will figure out the return type by itself. Next, call addValues and addValuesEx in our main. Verify the output of each function:

fun addValues(i: Int, j: Int): Int{
return i + j
}

fun addValuesEx(a : Int, b : Int) = a + b

fun main (args: Array<String>) {
var result = addValues(5,6)
println(result)

result = addValuesEx(5,6)
println(result)
}

Take another example. Write a function that takes two integer variables and returns the highest one. Return any value if both values are same:

fun getMaxEx(x: Int, y: Int) =
if(x >= y){
x
} else {
y
}

fun main (args: Array<String>) {
var val1 = 8
var val2 = 6

max = getMaxEx(val1,val2)
println("$val1 , $val2 : Max value is $max")
}

Writing a function as an expression always helps to remove unwanted code, but sometimes this convenience can be problematic. Add minor changes in the following function by adding a string value in if and else statements and executing it:

 fun getMaxExx(x: Int, y: Int) =
if(x >= y){
x
"Scary"
} else {
y
"Yes it is"
}

fun main (args: Array<String>) {
var val1 = 8
var val2 = 6
var maxEx = getMaxExx(val1,val2)
println("$val1 , $val2 : Max value is $maxEx")
}

This function returns Scary as output. When a function is written as an expression and no return type is defined in the function signature, the type inference comes into action, which means that Kotlin always returns the last line of code and the compiler evaluates the return type at runtime. If the line is an integer or a string, it will be decided accordingly. In this example, the expected output is an integer, but a string is returned. This tricky situation can be overcome by declaring the function return type:

fun getMaxEx(x: Int, y: Int) : Int =
if(x >= y){
x
"Scary"
} else {
y
"Yes it is"
}

If the return value is anything other than declared type, the compiler will throw the following error:

Type mismatch: inferred type is String but Int was expected

Functions with default arguments

Kotlin makes it possible to assign a value to a parameter in the function declaration. If the function is invoked without passing a value, then the compiler automatically assigns a default value to it. The hello function prints Hello Kotlin if no value is passed to the function:

fun hello(message : String = "Kotlin") : Unit{
println("Hello $message")
}

fun main (args: Array<String>) { hello()
hello("World")
}

The default argument is a very helpful feature in many situations. For example, consider a situation in which we are writing a currency exchange function that converts dollars into another currency and applies service charges on the conversion:

fun currencyExchange(dollar: Double, currencyRate: Double, charges: Double): Double {
var total = dollar * currencyRate
var fees = total * charges / 100
total = total - fees
return total
}

Let's assume that we want to convert 100 US dollars to Swedish krona. 1 dollar is equal to 10 Swedish krona, and our company charges 5% on the total amount:

fun main (args: Array<String>) {
var total = currencyExchange(100.0,10.0, 5.0)
println(total)
}

This function works fine; it multiplies the dollar by the target currency, calculates the charges, and returns the total amount after deductions. In the currency market, currency prices move quite rapidly, so it is a good idea to check the currency rate before conversion. However, its highly likely that conversion charges (5% in this example) will remain the same for a long period of time. If this is true, then the default value can be assigned to the charges variable, as follows:

fun currencyExchange(dollar: Double, currencyRate: Double, charges: Double = 5.0): Double {
var total = dollar * currencyRate
var fees = total * charges / 100
total = total - fees
return total
}

By setting the default value, the function can be invoked without a third parameter:

fun main (args: Array<String>) {
var total = currencyExchange(100.0,10.0)
println(total)

var
total = currencyExchange(100.0,10.0, 3.0)
println(total)
}

Functions with named parameters

Kotlin makes it possible to specify the argument's name in a function call. This approach makes the function call more readable and reduces the chance to pass the wrong value to the variable, especially when all variables have the same data type. To understand the importance of this feature, let's take the previous example of currency conversion:

fun currencyExchange(dollar: Double, currencyRate: Double, charges: Double = 5.0): Double {
var total = dollar * currencyRate
var fees = total * charges / 100
total = total - fees
return total
}

The currencyExchange function takes three parameters of the Double type—dollar, target currency, and conversion charges:

fun main (args: Array<String>) {
var total = currencyExchange(100.0, 6.0, 10.0)
println(total)
}

The currencyExchange function will perform the currency conversion and return the result. If the function contains a long list of variables as a parameter, then there is a high chance that values can be passed in the wrong order. In this example, the currency rate is swapped with conversion charges:

var total = currencyExchange(100.0, 6.0, 10.0)
println(total)
Output is 540 instead of 940 Swedish crown.

The program will execute without any errors because arguments passed to the function are the correct types but are in the wrong order. To solve this problem, Kotlin provides a feature called named parameters. Named parameters make it possible to pass different values to the function by explicitly defining a parameter's name. Using the argument's name helps to pass the correct value to each argument and makes the code clean and readable:

total = currencyExchange(dollar = 100.0, currencyRate = 10.0, charges = 6.0)
println(total)

By mentioning the name of each argument, function arguments can pass in any order:

fun main (args: Array<String>) {
var total = currencyExchange(dollar = 100.0, currencyRate = 10.0, charges = 6.0)
println(total)
total = currencyExchange(currencyRate = 10.0, charges = 6.0, dollar = 100.0)
println(total)
}

Functions and vararg

Kotlin allows programmers to pass arguments separated by commas to the function. These arguments are automatically converted into an array. This is called a vararg, a variable argument. Declare a vararg along with its type in the function declaration:

fun varargString(vararg list : String){
for (item in list){
println(item)
}
}

fun main (args: Array<String>) {
varargString("ett","tva","tre")
varargString("Sat","Sun","Mon")
}

As another example, write a function that takes an integer vararg as a parameter, adds it, and displays the total on the screen:

fun addVararg(vararg list: Int){
var total = 0
for (item in list){
total += item
}
println("Total $total")
}

fun main (args: Array<String>) {
addVararg(1,2,3,4,5,6,7,8,9,10)
}

vararg makes a programmer's life easier, especially when he or she is not sure about how many parameters may be required for one function:

fun add(a: Int, b: Int , c: Int , d: Int, e: Int)
fun add(vararg list : Int)

The first add function is restricted to the limited amount of variables declared in the function signature, but the add function with vararg can operate on all comma-separated values that we will pass to it.

vararg with other arguments

There is a possibility that we will be asked to create a function with an integer vararg along with two integer variables. See the following example:

fun trickyVararg(vararg list: Int, a : Int, b: Int){
var total = 0
for (item in list){
total += item
}

println("Total $total")
println("a = $a , b = $b")
}

fun main (args: Array<String>) {
trickyVararg(1,2,3,4,5)
}

The first three values (1,2,3) are for vararg list; parameter a is assigned with value 4 and parameter b is assigned with value 5. However, the compiler will throw the following errors:

Kotlin: No value passed for parameter 'a'
Kotlin: No value passed for parameter 'b'

vararg list is declared first in the function signature, and the compiler considers the all input for vararg list. This problem can be solved by declaring vararg as the last function argument:

fun trickyVararg(a : Int, b: Int, vararg list: Int){

var total = 0
for (item in list){
total += item
}
println("Total $total")
println("a = $a , b = $b")
}
fun main (args: Array<String>) {
trickyVararg(4,5,1,2,3)
}

The compiler will now assign the first two values to the a and b variables, and the rest will be assigned to vararg list. Declaring vararg at the end of the function signature is good practice, but it is not necessary. It can be declared at the beginning, but we must ensure that we call the rest of the variables by name:

fun trickyVararg02(vararg list: Int, a : Int, b: Int){
var total = 0
for (item in list){
total += item
}
println("Total $total")
println("a = $a , b = $b")
}

fun main (args: Array<String>) {
trickyVararg02(1,2,3,a=4, b=5)
}

Notice that by declaring the parameters' names in the function call, the compiler does not complain about missing values.

Package-level functions

Programmers with a Java background are familiar with static methods. Static methods are declared within a class and can be accessed directly by using class names as a reference. Kotlin does not have a static method, but it does provide a package-level function instead. To create a package-level function, do the following:

  1. Create a package.
  2. Create a file in the package.
  3. Create a function in the file.
  4. In the project explorer, select a folder where we want to add a package and then right-click New followed by Package.
  5. In the newly opened window, add a package name, for example, the Util package.
  6. Once the Util package (folder) is created, right-click on package and add the Kotlin file. Call this file MyUtil. Our Kotlin file is now ready to have package-level functions added to it.

Now open the MyUtil.kt file. We should see the following line here:

package Util

A directory or folder is called a package, which is where the Kotlin file resides. The package name is used as a reference to access the function. Let's create a function with a greeting message as follows:

package Util
fun hello() = println("Hello from Package Util")

The package-level function hello() has now been created and can be accessed by using the package name as follows:

PackageName.FunctionName()

Let's create the MyUtil package and add the MyTestUtil.kt file:

  1. Now open the MyTestUtil.kt file, add a main function, and call a package-level function, hello, to the file. A package-level function is always called using the package name as a reference:
fun main(args: Array<String>) {
Util.hello()
}
  1. Execute the program and verify the output, Hello from Package Util. Add different functions under the Util package as follows:
fun hello() = println("Hello from Package Util")

val PI = 3.1415926535 // Package level variable

// Calculate power of given number
fun myPow(base : Double, exp: Double) : Double {
var result = 1.0
var counter = exp
while (counter > 0) {
result*= base
counter--
}
return result
}

// Calculate area of a circle
fun areaOfCircle(radius : Double) : Double{
return PI * 2 * radius
}

// Generate random number within given range
fun myRandom(range: IntRange) : Int{
return range.shuffled().last()
}
  1. Call these functions in the main function and verify the result as follows:
fun main(args: Array<String>) {

Util.hello()

println("Power Function")
println(Util.myPow(5.0,3.0))

println("Random number generator")
var range = 1..50
for (i in 1..5) {
println(Util.myRandom(range))
}

println("value of PI is ${Util.PI}" )
println("Area of circle " + Util.areaOfCircle(4.0))
}

How to access a function

There are a couple of ways to access a package-level function. One way to do this is by using a package name with each function. This method has already been used in the previous example:

Util.hello()

The second way to access a package-level function is to import each function explicitly by using the import keyword:

import Util.hello
fun main(args: Array<String>) {
hello()
println("Power Function")
println(Util.myPow(5.0,3.0))
}

Notice that by adding import Util.hello, Kotlin allows us to use the hello function without using a package name with it. The third and most common way to import a package is with the wildcard:

import Util.*
fun main(args: Array<String>) {
hello()
println("Power Function")
println(myPow(5.0,3.0))
println("Random number generator")

var range = 5..50
for (i in 1..5) {
println(myRandom(range))
}

println("value of PI is ${PI}" )
println("Area of circle " + areaOfCircle(4.0))
}

This is the most convenient method. By using this approach, functions can be accessed by using a package name.