Kotlin Coroutines

This page is dedicated to Kotlin Coroutines, given the magnitude of the coverage.

Let’s check out the features…

Coroutines are more natural asynchronous routines

  • Looping constructs, yield control/cooperation are natural
  • Provides context for suspended functions
  • Exception handling is more sequential/natural
  • Scalability is easier
  • Mobile async development is easier
  • Non-preemptive multi-tasking

The below code shows two variations of starting threads, note that coroutines are lighter threads and completes far quicker and also don’t take too much of the CPU core’s scheduling.

import kotlinx.coroutines.*
import java.lang.Thread.sleep
import java.util.concurrent.atomic.AtomicInteger
import kotlin.concurrent.thread
import kotlin.system.measureTimeMillis
import kotlin.time.measureTime

const val num_tasks = 10000
const val loops = 500
const val wait_ms = 10L


fun main() = runBlocking {

    thread_main()

}

// normal thread - Doesnt completes and takes bit longer
fun thread_main() {

    println("Starting...")

    val result = AtomicInteger()
    val threads = mutableListOf<Thread>()

    val elaspedTime = measureTimeMillis {
        for (i in 1..num_tasks) {

            threads.add(thread {
                for (x in 1..loops) {
                    sleep(wait_ms)
                }
                result.getAndIncrement()
            })
        }

        threads.forEach { it.join() }
    }

    println("Result, ${result.get()} in ${elaspedTime} msecs")
}

// coroutine threads - Completes on my m/c 21 secs
 suspend fun  coroutine_main() = coroutineScope {

    println("Starting...")

    val result = AtomicInteger()
    val threads = mutableListOf<Job>()

    val elaspedTime = measureTimeMillis {
        for (i in 1..num_tasks) {

            // Threads not launched from main thread
            threads.add(launch(Dispatchers.IO) {
                for (x in 1..loops) {
                    delay(wait_ms)
                }
                result.getAndIncrement()
            })
        }

        threads.forEach { it.join() }
    }

    println("Result, ${result.get()} in ${elaspedTime} msecs")

}
  

Coroutines are built using coroutine builder aka runBlocking which provides a bridge between main / non-suspend vs suspend functions.

runBlocking is coroutine builder that blocks the main threads until all the code within it finishes.

Coroutines provides Job (similar to Future), can be used to join, wait, cancel etc.,.

fun main() = runBlocking {

    val job = launch {
        delay(1000)
        println("World")
    }
    println ("Hello..")
    job.join()
    println("Done")
}
// cancel job

@OptIn(ExperimentalTime::class)
fun main() = runBlocking {

    val elapsedTime = measureTime {
        val job = launch {
            repeat(1000) {
                delay(10)
                println(".")
            }
        }

        delay(250)
        // Note cancel will be called coroutine delay which is built in function that will
        // cooperate to cancel the job
        job.cancel()
        job.join() // or job.cancelAndJoin()
    }
    println("Done in ${elapsedTime} secs")
}
// Timeout handling
fun main() = runBlocking {


    val job = withTimeoutOrNull(100) {

        repeat(1000) {
            yield()

            println(".")
            Thread.sleep(1)
        }

    }

    if (job == null) {
        println("Timeout...")
    }

}

coroutineScope – create parallel launches and waits until all the jobs are completed.

fun main() = runBlocking {

    launch {
        coroutineScopeTest()
        println("Finished.")
    }

    println("Done")

}

suspend fun coroutineScopeTest()  {
    coroutineScope {
        launch {
            delay(1000)
            println("First task completed")
        }

        launch {
            delay(2000)
            println("Second job completed")
        }
    }

    println("Task completed")
}

Dispatchers – determine which thread coroutine can run on…

  • Default (Runs on Fork/Join pool – CPU bound)
  • Main (Runs on Main thread)
  • IO (IO thread pool)
  • Other (custom, JavaFX, ..,)
// Create own dispatcher and use them launch the jobs
fun main() = runBlocking{

    val dispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()

    val job = launch(dispatcher) {
        println("Running in thread, ${Thread.currentThread().name}")
    }

    job.join()
    println("Done")
    
    dispatcher.close()
}

CoRoutine withContext – used within suspend function

fun main() = runBlocking{

    // Coroutine Contexts - withContext
    val dispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
    val job = launch(dispatcher) {
        println("Calling with thread, ${Thread.currentThread().name}")
        doWork()
    }

    job.join()

    println("Done")

}

suspend fun doWork() {
    withContext(Dispatchers.IO) {
        println("Dispatcher IO running in thread, ${Thread.currentThread().name}")
    }
}

Kotlin

Here are some tips on this new wonderful language…

  • if statement can be an assignment, as every operator is a function within Kotlin, so for instance,
val message  = if ( a== b) "True" else "False"
println (message)

Null Safety

By default, references cannot be assigned to null, Kotlin will throw compile time error.

To make an object reference hold null, we need to explicitly state, something like a question mark which indicates to the compiler that the reference can take on null values

var answer:String? = null

class Book {
  var title: String
}

val book:Book? = Book()
book?.title = "Group"

When Statement

Kotlin introduces powerful when operator, which acts like switch but can type reference, so something like,

val correctAnswer = "42"
var answer = "42"

when(answer) {
  CorrectAnswer -> println("Correct answer")
   else -> println("Something else")
}

Try/Catch

val number:Int = try {
  parseInt(someNumber) } catch(e: NumberFormatException) {
  null
}

println("The result is $number")

For Loops

// Declare ranges for not just numbers, anything Comparable
var rangea = 'a' ..'z'
var inRange = 1..10

for(i in 10 downTo 1 step 2) {
  println(i)
}

for(i in 1..10 step 2) {
  println(i)
}

for( i in 1 until 10) { // doesnt include 10, 
  println(i)
}

//List
var numbers = listOf(1,2,3,4,5)
for( i in numbers) {
  println(numbers)
}

// For loop with index
for((index, element) in numbers.withIndex() {
  println("$index = $element")
}

//Map
var ages=TreeMap<String, Int>[]
ages['Kevin"] = 55
ages["Sam"] = 24
for((name, age) in ages) {
   println("$name = $age")
}

Functions

Calling from Java

// Create package class and functions, Program.kt

@file:JvmName("DisplayFunctions")

package orm

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

// Now from within the Java class, Test.java

import orm.*;

public class Test {

  public static void main(String args[]) {
    System.out.println(DisplayFunctions.display("Hello from Java");
  }

}

Functions can use default parameter

fun display(message:String, logLevel: Int = 0) {
  println(message)
}

For calling from Java, add annotation
@JvmOverloads
  println(message)
}

// Now this function can be called from Java with/without logLevel

Named Parameters

Extension Functions

// Functions can be added to existing classes, essentially static functions
// 1. Cut down on utility classes
// 2. Code easier to read

fun main() {
  val text = "Replace    text  tt. of. this "

  // Normal usage of function call
  println(replaceWithRegex(text))

  // Extension usage
  println(text.replaceWithRegex())

}

fun replaceWithRegex(value:String) : String {
  val regex = "\\s+"
  regex.replace(value, "")
}

// Two changes:
// 1. Add class that needs extension, in this case, String prefix'd to the function name
// 2. Remove the method arg, replace with this
fun String.replaceWithRegex() : String {
  val regex = "\\s+"
  regex.replace(this, "")
}

Infix Function

data class Header(val name: String) 

infix fun Header.plus(other: Header) : Header {
  return this.name + other.name
}

h1, h2, h3: Header

h3 = h1 plus h2

// Also support operator overloading

operator infix Header.plus(other: Header) : Header {
  .....
}

// Now, this can be called as

h3 = h1 + h2

Tail Recursive

// Famous Fibonacci Series

tailrec fun fib(n:Int, a:BigInteger, b:BigInteger) : BigInteger = {
  if (n == 0) b else fib(n-1, a+b, a)
}

Interfaces

  • Default Methods
  • Properties
interface Time {

  fun setTime(....)

  // default method
  fun time() = { ....
   }

// Diamond interface problem

interface A { fun doSome() = { } }
interface B { fun doSome() = { } }
class Foo : A,B = {
  override fun doSome() = {
    super<A>.doSome()
  }
}

Class Construction

open class Person(val name: String) {
  val name: String
  init {
    this.name = name
  }
}

open class Person(val name: String) {
  val _name = name
}

open class Person(val name: String)

// Overloaded constructors
open class Person(val name: String) {
  constructor class Person(name: String, age:Int) : this(name) 
}

// Class extending base class
class Student(name: String) : Person(name)

class Student: Person {
  constructor(name: String) : super(name)
}

class Student ; Person() 

Data Classes

Immutable Classes

Provides, Equals, Hashcode, ToString

Kotlin provides copy methods

data class Meeting (val name: String, val location: String)

val aMeeting = Meeting("Yepp", "London")
val aaMeeting = aMeeting.copy(location = 'New York")

Object Classes

‘object’ classes are used the same as a class but they don’t have a constructor, essentially providing Singleton type instantiation

  • Create Singletons
  • Derive from classes
  • Implement interfaces
  • Can be created inside class

data class Course(val id:Int, val name: String)

object Courses {

    var allCourses = arrayListOf<Course>()

}

class Grad(firstName: String, lastName: String, tutor: String) {

    fun enrole(courseName: String) {

        Courses.allCourses.add(Course(1, courseName))
        
       val courses = Courses.allCourses.filter { it.id > 0 && it.name == courseName}.firstOrNull()

    }

}

Companion Object

Essentially companion object provides static methods & interfaces for the enclosing classes, and annotate with @JvmStatic if main method wants to be called from outside, for instance

class Program {
  companion object {
    @JvmStatic
    fun main(args: Array<String>) {
      println("Hello")
    }
  }
}

High Level Functions

  • Functions as first class
  • Can pass to and from functions
  • Can store in collections
fun main() {

    val fibCalc = fib(11, object: Process {
        override fun execute() {
            println("Inside execute...")
        }
    })

   
}

interface Process {
    fun execute()
}

fun fib(item:Int, action: Process) {
  // fibonacci calculation
}
// Lambda style invoking the functions
fun main() {

    // using lambda style

    val fibCalc1 = fib(11) { s -> println(s)  }

    val fibCacl2 = fib (11) { println(it) }

    val fibCalc3 = fib(11, ::println)
}

interface Process {
    fun execute(item:String)
}

fun fib(item:Int, action: (String) -> Unit) {
  // fibonacci calculation
}

Closures

Kotlin lambdas can mutate values


fun main() {
    
    var total  = 0;
    
    // Note within closure, field can be mutated
    // unless in Java, it has to be declared final.
    fib(11, { it -> total+it })
}


interface Process {
    fun execute(item:String)
}

fun fib(item:Int, action: (Int) -> Unit) {
    // fibonacci calculation
}

with and apply functions

class Meeting(var meeting: String, var from : LocalDate, var addOns: List<String>) {

    fun create(): Meeting {
        return Meeting("hello", LocalDate.now(), arrayListOf())
    }
}

fun main() {

    var m:Meeting = Meeting("", LocalDate.now(), listOf())

    with(m) {
        meeting = "New Meeting"
        from = LocalDate.now()
        addOns = arrayListOf("Test")
    }
    
    m.apply {
        meeting = "New Meeting"
        from = LocalDate.now()
        addOns = arrayListOf("Test") 
    }.create()
}

Kotlin Collections { Filter, Map, FlatMap}

Infinite Collections

  • Sequences – same as streams in Java
  • Lazily evaluated
  • Prefer to normal collections
  • Java 8 streams arent available on Andriod*
fun main()  {

    val meetings = listOf<Meeting>(
        Meeting(1, "A"),
        Meeting(2, "B")
    )

    meetings.asSequence()
        .filter { it.name == "A" }
        .map { it.id }
        .forEach { println(it) }
}

Kotlin <-> Java InterOperable

Java Code

public interface Created {

    public void onCreate(User user);
}

public class User {

    private final String name;


    public User(String name) {
        this.name = name;
    }

    public void create(Created created) {
        created.onCreate(this);
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

Kotlin -> Java Callback

fun main() {

    val user = User("Raghu Ram")

    user.create{ println ("user $it has been created")}

}

Nullability

  • Supports nullable types
  • Only explicit variables can be null
  • By default, variables are non-null

To decalre null variable, declare

val m:Meeting?

// Need to use nullable checks on method invocation
// Safe call (?.)
m?.close()

Elvis Operator

val newMeeting = m?:Meeting()

Safe Cast

val saveable = o as? ISavable

Not-Null Assertions

// Asserts that m is not null, if null NPE at operator is used
m!!.close()

let assertion for nullable types

// m is nullable type, compiler will allow to call the underlying non-nullable method, onyl when
// m is not null
m?.let {
  closeMeetingNonNull(m)
}

lateinit

class Meeting {

  // Note it's not initialized and using lateinit to inform compiler it will be initilialized
  lateinit var address: Address

  fun init(addr: Address) {
    address = addr
   }

}

Java <-> Kotlin : @NotNull, @Nullable

Java Class

class Meeting {
 private String title;

  public void addTitle(@NotNull title) {
    this.title = title
  }

  @Nullable
  public String getTitle() {
    return title;
  }
}

Kotlin code:

fun main() {
  val meeting: Meeting? = Meeting()
  meeting.addTitle("Some value");

  // Not allowed
  meeting.addTitle(null)

  // Can be null
  println(meeting.getTitle())
}

Kotlin Collections

  • Collections can be Nullable
  • Hold Null Values
  • Read-only or Mutable
  • Interop with Java
  • Arrays within Collections
Arrays can be created with
  - arrayOf
  - arrayOfNulls
  - Array() function

  - IntArray
  - ByteArray
  - CharArray
  - etc

 -- Provides same functions on Arrays same as Collections

High Order Functions

val action: () -> Unit = { println("Hello, World") }
val calc: (Int, Int) -> Int = { x: Int, y -> Int -> x * y }

inLine Functions

Used to substitute inline functions, add the command inline before the function name

Generics

// Generics Methods
fun <T> List<T>.itemAt(ndx: Int) : T {
  return this[ndx]
 }

// Generics Class
class Node<T>(private val item:T) {
  fun value(): T {
    return item
  }
}

Kotlin can remember the reified information at runtime using this command

inline fun <reified T> foo(value: Any) = value is T

inline fun <reified T> List<*>.typeOf() : List<T> {
  val returnList = mutableListOf<T>()

  for (item in this) {
    if (item is T) {
      returnList.add(item)
    }
  }
}

Co and contra variance Generics

// Note, need to use 'out', so to inform comopiler that no changes will be done to the 
class AllMeetings<out T:Meeting> (val meetings: List<Meeting>) {
  val count: Int get() = meetings.count()

  operator fun get(i: Int) = meetings[i]
 }

fun attendMeetings(meetings: AllMeetings<Meeting>) {
  ....
}

fun main() {
  var financialMeeting: FinanceMeeting = ...
  attendAllMeeting(financialMeeting)
}