Kotlin & Coroutines

Kotlin is a statically typed, open-source programming language that runs on the JVM and can be used anywhere Java is used today. It can be compiled using Java source code

The most popular features of kotlin are:

  • Kotlin is Concise: reduces the writing of the extra codes
  • Compact code: Kotlin is an OOPs-based programming language. Less number of code compared to java.
  • Kotlin is Simple: Compiling the code is simple, resulting in improved performance for Android development.
  • Null safety: Kotlin is a null safety language. It aims to eliminate the NullPointerException (null reference) from the code.
  • Java Interoperability: Kotlin provides full interoperability for Java code. Java code can utilize Kotlin code, and Kotlin code can use Java code.
  • Compilation Time: Kotlin is faster and better than Java in terms of its performance and fast compilation time.
  • Default & Named Parameters
Difference between Val and Constant

Both val and const are immutable. 
const is used to declare compile-time constants, whereas val for run-time constants.

const val VENDOR_NAME = "Kifayat Pashteen"  // Assignment done at compile-time
val values = getValues() // Assignment done at run-time
ENUM
Enum is used to store same type of constant values. Enum constants are objects of enum 
class type
The properties of Enum object are Ordinal and Name
Enums object have 2 default methods : values() & valueOf() where values returns all the enum
objects in the form of an array
More often used within WHEN statements
We can define an interface within an ENUM class Enum class can implement interface but they cannot inherit from abstarct classes or open classes

enum class CreditCardType{
SILVER, //ordinal = 0, name = SILVER
GOLD, //ordinal = 1, name = GOLD
PLATINUM //ordinal = 2, name = PLATINUM
}

//If required to add more properties to the ENUM other than Name and Ordinal
enum class CreditCardColor(val color : String, val maxLimit :Long = 100000):ICardCashBack{
SILVER("Gray", 50000) { //ordinal = 0, name = SILVER
override fun getCashBackValue(): Float = 0.02f
},
GOLD("Gold") { //ordinal = 1, name = GOLD
override fun getCashBackValue(): Float = 0.04f
},
PLATINUM("Black") {
override fun getCashBackValue(): Float = 0.06f
} //ordinal = 2, name = PLATINUM
}

interface ICardCashBack {
fun getCashBackValue() : Float
}

fun main() {
val cardType : CreditCardType = CreditCardType.GOLD

// Enum object Properties
println(CreditCardType.GOLD.ordinal) // Return 1
println(CreditCardType.GOLD.name) // Return GOLD
println(CreditCardType.GOLD) // Return GOLD

// Enum Object Functions
val dataList: Array<CreditCardType> = CreditCardType.values()
dataList.
forEach { println(it) }

// When statement
when(cardType) {
CreditCardType.
SILVER -> println("Card Type is ${CreditCardType.SILVER}")
CreditCardType.
GOLD -> println("Card Type is ${CreditCardType.GOLD}")
CreditCardType.
PLATINUM -> println("Card Type is ${CreditCardType.PLATINUM}")
}

println(CreditCardColor.GOLD.color)
}
Sealed Class

A sealed class is one that has a set of subclasses. Sealed classes constructors are by default private. Due to the fact that a sealed class is automatically abstract, it cannot be instantiated

Sealed classes are like enum classes but with few restrictions
Sealed class is accessible only within that particular file in which it is declared
Within a sealed class, we can have a regular class and data class as well
Also we can have object declaration as well which inherits the sealed class
The subclasses of sealed classes must be declared in the same file in which
sealed class is itself declared
The subclasses can be a data class, regular class, object class or even another
sealed class or sealed interface
Sealed class ensures type-safety by restricting the set of types at compile time only
A sealed class is implicitly an abstract class which cannot be instantiated
By default the constructor of sealed class is private and we cannot make it non-private

sealed class Shape {
class Circle(var radius : Int) : Shape()
class Square(var side : Int) : Shape()

object Cylinder : Shape() // Singleton
sealed class Line : Shape()
sealed interface Draw
}

class Rectangle(var length : Int, var breadth : Int) : Shape()

fun main() {
// Creating the instances of the classes defined within the sealed class
var circle = Shape.Circle(8)
var square = Shape.Square(8)
var rectangle = Rectangle(8, 9)

val cylinder = Shape.Cylinder

checkShape(circle)
}

fun checkShape(shape : Shape) {
when(shape) {
is Shape.Circle ->                 println("The area of circle is ${3.14 * shape.radius * shape.radius} ")
is Shape.Square -> println("The area of square is ${shape.side * shape.side}")
is Rectangle -> println("The area of rectangle is ${shape.length * shape.breadth}")
Shape.Cylinder ->
println("This is a cylinder")
}
}
Constructors
Primary Constructors :
This type of constructor is initialised in the class header and is provided after the class name. It is declared using the “constructor” keyword. Parameters are optional in this type of constructor
class Sample constructor(val a: Int, val b: Int) {
// code
}
If no annotations or access modifiers are provided, the constructor keyword can be omitted. The initialization code can be placed in a separate initializer block prefixed with the init keyword because the primary constructor cannot contain any code. If we want to perform any operations as soon as the object is created, then we need to declare them in the init block
fun main(args: Array<String>) {
val s1 = Sample(1, 2)
}
class Sample(a : Int , b: Int) {
val p: Int
var q: Int
// initializer block
init {
p = a
q = b
println("The first parameter value is : $p")
println("The second parameter value is : $q")
}
}

Output : 
The first parameter value is: 1
The second parameter value is: 2
The values 1 and 2 are supplied to the constructor arguments a and b when the object s1 is created for the class Sample. In the class p and q, two attributes are specified. The initializer block is called when an object is created, and it not only sets up the attributes but also prints them to the standard output

Secondary Constructors :
Secondary constructors allow for the initialization of variables as well as the addition of logic to the class. They have the constructor keyword prefixed to them
fun main(args: Array<String>) {
val s1 = Sample(1, 2)
}
class Sample {
constructor(a: Int, b: Int) {
println("The first parameter value is : $p")
println("The second parameter value is : $q")
}
}
Output: 
The first parameter value is: 1
The second parameter value is: 2

Iterating over Data Structure :
For Loop : Check for even number
fun main(args: Array<String>) {
var numbersArray = arrayOf(1,2,3,4,5,6,7,8,9,10)

for (num in numbersArray){
if(num % 2 == 0){
print("$num ")
}
}
}
While Loop : Print 1 to 5
fun main(args: Array<String>) {
var number = 1
while(number <= 5) {
println(number)
number++;
}
}
Do While Loop : Sum of first four numbers
fun main(args: Array<String>) {
var number = 4
var sum = 0

do {
sum += number
number--
}while(number > 0)
println("Sum of first four natural numbers is $sum")
}

Null Safety
There are 4 types :
  • Safe Call Operator(?.)
  • Safe Call with let Operator(?.let {..})
  • Elvis Operator(?:)
  • Not Null Assertion(!!)
fun main() {
val name : String? = null

/*
1. Safe Call(?.)     It allows us to use a single expression to perform both a null check and a method call
    Returns the length if name is not-null else returns Null
Use it if we don't mind getting null value
*/
println("The length of entered string is ${name?.length}")          name?.toLowerCase() is equivalent to     if(name != null)      name.toLowerCase()     else     null

/*
2. Safe Call with let(?.let {..})
Executes the block only if the Name is not null
*/
name?.let {
println("The length of entered string is ${name.length}")
}

/*
3. Elvis operator(?:)     When the original variable is null, the Elvis operator is used to return a non-null value    or a default value. In other words, the elvis operator returns the left expression if it     is not null, otherwise, it yields the right expression. Only if the left-hand side     expression is null is the right-hand side evaluated.
When we have nullable reference name, we can say 'is name not null', use it else
use some non null value
*/
val len = if(name != null) {
name.length
} else {
-
1
}

val length = name?.length ?: -1
println("The length of entered string is $length")

/*
4. Not Null assertion(!!)     If the value is null, the not null assertion (!!) operator changes it to a non-null type     and throws an exception.
Use when we are sure the value is not null. If Null, will throw NullPointerException
*/
println("The length of entered string is ${name!!.length}")     // If the value of name is not null
    fun main(args: Array<String>) {     var sample : String? = null     str!!.length     }     Exception in thread "main" kotlin.KotlinNullPointerException
}

Scope Functions : 
Scoped functions are functions that execute a block of code within the context of an object.

How to distinguish between scope functions?
  • The way to refer to the context object(Either this or it)
  • The return value(Either Context Object or Lambda Result)

The types of scope functions are :
  • WITH - If we want to operate on a non-null object. When calling functions on context objects without supplying the lambda result, ‘with' is recommended
  • LET - If we want to just execute lambda expression on a Nullable Object and avoid NullPointerException
  • RUN - If we want to operate on a Nullable Object, execute lambda expression and avoid NullPointerException. It is a combination of the ‘let' and ‘with' functions.
  • APPLY - If we want to initialize or configure an object. It can be used to operate on receiver object members, primarily to initialise them.
  • ALSO - If we want to do some additional object configuration or operations. It's used when we need to do additional operations after the object members have been initialised.

  • WITH - Return : Lambda Result, Context Object : this
    class Person {
    var name : String? = "Keshav"
    var age : Int? = 10
    }

    fun main() {

    /*
    * Property 1 : Refer to Context Object by using 'this'
    * Property 2 : The return value is 'lambda result'
    * */

    val person = Person() // Ordinary Code
    println(person.age)
    println(person.name)

    with(person) {
    println(this.name)
    println(this.age)
    }

    with(person) { // this keyword is not required as its internally available
    println(name)
    println(age)
    }

    val ageAfterten : Int = with(person) { //Since it returns lambda result
    println(name)
    println(age)
    age + 10
    }

    println("Age after 10 Yrs : $ageAfterten")
    }
  • APPLY - Return : Context Object, Context Object : this
    class Person {
    var name : String? = null
    var age : Int? = null
    }

    fun main() {
    /*
    * Property 1 : Refer to Context Object by using 'this'
    * Property 2 : The return value is 'Context Object'
    * */
    val person = Person() // Ordinary Code
    person.age = 10
    person.name = "Keshav" // Using Scope Function APPLY. person object refers to the Context Object
    val person = Person().apply {
    age = 10
    name = "Keshav"
    } // We can use WITH function to display the data
        with(person) {
    println(name)
    println(age)
    }
    }

  • RUN - Return : Lambda Result, Context Object : this
    Is a combination of WITH and LET function. Use it when we want to operate on a Nullable object and avoid NullPointerException
    class Person {
    var name : String = "Keshav"
    var age : Int = 10
    }

    fun main() {

    /*
    * Property 1 : Refer to Context Object by using 'this'
    * Property 2 : The return value is 'Lambda Result '
    * */

    val person : Person? = Person()

    val details = person?.run { // Instead of LET, we use RUN function and like with,
    it returns the lambda result
    println(age)
    println(name)
    age + 10
    }

    println(details)
    }
  • ALSO -  Return : Context Object, Context Object : it
    Used when we need to perform some additional operation on an object after we have initialized it
    class Person {
    var name : String = "Keshav"
    var age : Int = 10
    }

    fun main() {

    /*
    * Property 1 : Refer to Context Object by using 'it'
    * Property 2 : The return value is 'Context Object'
    * */

    //Suppose we have a list
    val numberList : MutableList<Int> = mutableListOf(4, 9, 7)
    // Now suppose we perform some operation on the numberList object and then we
    // decide to do some operation on the numberList object
    println("The elements are $numberList")
    numberList.add(5)
    println("The elements are $numberList")
    numberList.removeAt(2)
    println("The elements are $numberList")
    //Using ALSO function.Context Object is IT, instead of calling numberList all the time
    val updatedList = numberList.also { // Returns the context Object - updatedList
    it.add(6)
    println("The elements are $it")
    it.removeAt(1)
    println("The elements are $it")
    }

    val person = Person().apply {
    name = "Pai"
    age = 25
    }

    with(person) {
    println(age)
    println(name)
    }

    person.also { //Using ALSO function. The Context Object is IT instead of calling
    person all the time
    it.name = "Keshav"
    it.age = 35
    println("The updated age is ${it.age} & Name is ${it.name}")
    }
    }
  • LET - Return : Lambda Result, Context Object : it
    Use LET function to avoid NullPointerException
    The let function is frequently used for null safety calls. For null safety, use the safe call operator(?.) with ‘let'. It only runs the block with a non-null value.
    fun main() {

    /*
    * Property 1 : Refer to Context Object by using 'it'
    * Property 2 : The return value is 'Lambda Result '
    * */

    var myName : String? = null
    println(myName!!.reversed()) // Here myName is NULL and the application will crash

    myName = "keshav"
    val stringLength = myName?.let { // The Context Object it IT
    println(it.reversed())
    println(it.capitalize())
    println(it.length)
    it.length // Since it returns the Lambda Result
    }
    println(stringLength)
    }

Collections :
Immutable Collections : Read Only Operations
  • Immutable List : listOf
  • Immutable Map : mapOf
  • Immutable Set : setOf
Mutable Collections : Read & Write Operations
  • Mutable List : ArrayList, arrayListOf, mutableListOf
  • Mutable Map : HashMap, hashMapOf, mutableMapOf
  • Mutable Set : hashSetOf, mutableSetOf
var myArray = Array<Int>(5) { 0 }    // Mutable but fixed size
myArray[0] = 43
myArray[2] = 56
myArray[4] = 46

for(element in myArray) {    // Using Individual Element
println(element)
}

println()

for(index in 0..myArray.size-1) {    // Using Index
println(myArray[index])
}

Infix Function :

All infix functions are extension functions but not all extension functions are infix functions
They have just one parameter defines in the function
fun main() {
val x : Int = 10
val y : Int = 20

val greaterValue = x greaterValue y // Same as x.greaterValue(y)
println(x.greaterValue(y))
}

infix fun String.add(s1 : String, s2 : String) : String { // Not infix cos 2 parameters
return this + s1 + s2
}

infix fun Int.greaterValue(other : Int) : Int { // Infix cos only 1 parameter
if (this > other)
return this
else
return other
}

lateinit Keyword : 

lateinit stands for late initiation. If we don't want to initialize a variable in the constructor and instead want to do it later, we can use the lateinit keyword to declare that variable. It won't start allocating memory until it's been initialized. Lateinit cannot be used for primitive type attributes like Int, Long, and so on. Since the lateinit variable will be initialized later, we cannot use val. If we try to access a lateinit property before it has been initialized, a special exception is thrown that explicitly identifies the property and the fact that it hasn't been initialized.

  • Used with mutable data type(var) - lateinit val name : String -> Not Allowed
  • Used with non nullable data type - lateinit var name : String? -> Not Allowed
  • Must be initialized before we use it
fun main() {
val country = Country()
println(country.name) //lateinit not initialized. UninitialisedPropertyAccessException

country.name = "India"
println("The name is ${country.name}")
}

class Country {
lateinit var name : String
}

Lazy Initialization
  • Used to prevent unnecessary initialization of objects
  • Our variables will not be initialized unless we use it in our code
  • Initialized only once. Next time when we use it, it will get the value from cache memory
  • Its thread safe. It is initialized in the thread where it is used for 1st time. Other threads use the same value that is stored in the cache
  • The variable can be var or val, nullable or non-nullable
val pi : Float? by lazy {
3.14f
}
val pi : Float by lazy {
3.14f
}

fun main() {
val area = pi * 4 * 4
println("The area is $area")
}

There are some classes whose object initialization is so time-consuming that it causes the entire class creation process to be delayed. Lazy initialisation helps in such problems. When we declare an object using lazy initialisation, the object is initialised only once when the object is used. If the object is not used throughout, the object is not initialised. This makes the code more efficient and faster. 
class FastClass {
private val slowObject: SlowClass by lazy {
println("Slow Object initialised")
SlowClass()
}

fun access() {
println(slowObject)
}
}
fun main(args: Array<String>) {
val fastClass = FastClass()
println("FastClass initialised")
fastClass.access()
fastClass.access()
}
Output:
FastClass initialised 
Slow Object initialised 
SlowClass@2b12fkk7 
SlowClass@2b12fkk7

In the above code, we have instantiated an object of the SlowClass inside the class structure of the FastClass using lazy initialisation. The object of the SlowClass is generated only when it is accessed in the above code, that is, when we call the access() method of the FastClass object and the same object is present throughout the main() method.

lateInit vs Lazy Initilaisation

Extension Functions
The ability to add more functionality to the existing classes, without inheriting them. When a function is added to an existing class it is known as Extension Function. To add an extension function to a class, define a new function appended to the classname
// A sample class to demonstrate extension functions
class Circle (val radius: Double){
// member function of class
fun area(): Double{
return Math.PI * radius * radius;
}
}
fun main(){
// Extension function created for a class Circle
fun Circle.perimeter(): Double{
return 2*Math.PI*radius;
}
// create object for class Circle
val newCircle = Circle(2.5);
// invoke member function
println("Area of the circle is ${newCircle.area()}")
// invoke extension function
println("Perimeter of the circle is ${newCircle.perimeter()}")
}

We don't have a method named "perimeter" inside the "Circle" class but we are implementing the same method outside of the class. This is all because of function extension

Can 'add' functions to a class without declaring it
The new functions added behaves like static
fun main() {
val str1 : String = "Hello "
val str2 : String = "World"
val str3 : String = "Hey "

println(str3.add(str1, str2))

val i1 : Int = 10
val i2 : Int = 20

val greaterValue = i1.greaterValue(i2)
println(i1.greaterValue(i2))
}

fun String.add(s1 : String, s2 : String) : String {
return this + s1 + s2
}

fun Int.greaterValue(other : Int) : Int {
if (this > other)
return this
else
return other
}

class Sample {
var str : String = "null"
fun printStr() {
print(str)
}
}
// function extension
fun Sample.add(a : Sample):String{
var temp = Sample()
temp.str = this.str + " " +a.str
return temp.str
}
fun main(args: Array<String>) {
var a = Sample()
a.str = "Keshav"
var b = Sample()
b.str = "Pai"
var c = Sample()
c.str = a.add(b)
c.printStr()
}

We don't have a method named "addStr" inside the "Sample" class in the preceding example, but we are implementing the same method outside of the class. This is all because of function extension
Companion Object :
In Java, the static keyword is used to declare class members and utilise them without creating an object
In Kotlin, there is nothing called the “static” keyword. So, if we want to achieve the functionality of static member functions, we use the companion objects
We must use the companion keyword in front of the object definition to construct a companion object
class CompanionClass {
companion object CompanionObjectName {
// code
}
}
val obj = CompanionClass.CompanionObjectName
We can also remove the CompanionObject name and replace it with the term companion, resulting in the companion object's default name being Companion
class CompanionClass {
companion object {
// code
}
}
val obj = CompanionClass.Companion
All the required static member functions and member variables can be kept inside the companion object created
class Sample {
companion object Test {
var a: Int = 1
fun testFunction() = println("Companion Object’s Member function called.")
}
}
fun main(args: Array<String>) {
println(Sample.a)
Sample.testFunction()
}
Output :
1
Companion Object’s Member function called

Differentiate between open and public keywords in Kotlin

The open annotation on a class is the opposite of the final annotation in Java. By default, a class cannot be inherited in Kotlin. In Kotlin, an open method signifies that it can be overridden, whereas it cannot be by default. Instead, any methods in Java can be overridden by default
In Kotlin, all the classes are public by default. If no visibility modifier is specified, public is used by default, which means our declarations will be accessible everywhere inside the program.

When Keyword
The 
“when” keyword is used in Kotlin to substitute the switch operator in other languages such as Java. When a certain condition is met, a specific block of code must be run

fun main(args: Array<String>) {
var temp = "Interview"
when(temp) {
"Interview" -> println("Interview went good")
"Job" -> println("Received a job.")
"Success" -> println("Cleared the interview")
}
}

In the above code, the variable temp has the value “Interview”. The when condition matches for the exact value as that of temp’s and executes the corresponding code statements. Thus, “Interview went good” is printed

Properties and Advantages : 

They can become part of our own class eg : Student, Employee
They can become part of predefined class eg : String, Int etc
Reduces the code
Code is much cleaner and easy to read

Structural Equality(==) & Referential Equality(===)
(==) - Uses equals() method to compare equality of 2 objects in terms of values
(===) - Checks if 2 reference variables points to the same object or not

When to prefer Property over Function & Vice Versa in Kotlin?
  • Prefer to use val(Read Only) property over functions in following cases :
    No arguments required
    Throwing an exception is not required
    Cheap Computation
    Returns same result when called (provided the object state hasn't changed)
    class Box(val height : Int, val width : Int, val length : Int) {
    var usedSpace : Int = 0
        // Satisfies all the above points. Hence the below code is preferred
    /*fun getBoxVolume() : Int {
    return height * width * length
    }*/

    val volume : Int
    get() = height * width * length

    // Cannot be done since the usedSpace is VAR and the value keeps changing /*val space : Int
    get() = volume - usedSpace*/

    fun availableSpace() : Int {
    return volume - usedSpace
    }
    }

Single Line Functions(Function as Expression)
fun greaterNum(a : Int, b : Int) : Int {
if(a > b)
return a
else
return b
}

fun greaterNum(a : Int, b : Int) : Int = if(a > b) a else b

VAL is Read-Only but not Immutable :

class Box() {
val height = 10 // Read-Only and Immutable

val width : Int // Read- Ony but not Immutable
get() = Random.nextInt(1, 15)
}

fun main() {
val box = Box()
box.height = 20 // Error
println(box.width) //10

box.width = 15 // Error
println(box.width) //4
println(box.width) //7
println(box.width) //11
}

Data Class

A data class is a list of data set allocation attributes and their values. You cannot assign a data class to an object. The Data class is a simple class that holds data and provides typical functions. To declare a class as a data class, use the data keyword. 
data class className ( list_of_parameters) To ensure consistency, data classes must meet the following requirements:

  • At least one parameter is required for the primary constructor
  • val or var must be used for all primary constructor parameters
  • Abstract, open, sealed, or inner data classes are not possible
  • Only interfaces may be implemented by data classes
Eg:
data class Sample(var input1 : Int, var input2 : Int)
The above code snippet creates a data class Sample with two parameters.
fun main(agrs: Array<String>) {
val temp = Sample(1, 2)
println(temp)
}
Here, we create an instance of the data class Sample and pass the parameters to it.
Output : Sample(input1=1, input2=2)

initialize an array
val numbers: IntArray = intArrayOf(10, 20, 30, 40, 50)

ForEach
var courseList = listOf("Android", "Java", "Kotlin")
courseList.forEach {
println(it)
}

Difference between FlatMap and Map

  • FlatMap is used to combine all the items of lists into one list.
  • Map is used to transform a list based on certain conditions.

Difference between List and Array

If you have a list of data that is having a fixed size, then you can use an Array. But if the size of the list can vary, then we have to use a mutable list.

Visibility modifiers

  • private: visible inside that particular class or file containing the declaration.
  • protected: visible inside that particular class or file and also in the subclass of that particular class where it is declared.
  • internal: visible everywhere in that particular module.
  • public: visible to everyone.

Lambdas expressions

Lambdas expressions are anonymous functions that can be treated as values i.e. we can pass the lambdas expressions as arguments to a function return them, or do any other thing we could do with a normal object

val add : (Int, Int) -> Int = { a, b -> a + b }
val result = add(9, 10)

Difference between fold and reduce
  • fold takes an initial value, and the first invocation of the lambda you pass to it will receive that initial value and the first element of the collection as parameters.

    listOf(1, 2, 3).fold(0) { sum, element -> sum + element }

    The first call to the lambda will be with parameters 0 and 1.

    Having the ability to pass in an initial value is useful if we have to provide some sort of default value or parameter for our operation.

  • reduce doesn't take an initial value, but instead starts with the first element of the collection as the accumulator (called sum in the following example)

    listOf(1, 2, 3).reduce { sum, element -> sum + element }

    The first call to the lambda here will be with parameters 1 and 2

Higher Order Functions & Lambdas
  • Can accept functions as parameters
  • Can return a function
  • Can accept function as a parameter and return a function as a value
Lambda Expressions :
Its a function with no name

val myLambda (Int) -> Unit = {s : Int -> println(s)}
Program to display the length of the string using higher order function by accepting String 
array and lambda expression as input

fun main() {
val data = listOf("Keshav", "Pai")
var result = IntArray(data.size)
val lambda : (List<String>) -> IntArray = { data ->
for(i in 0..data.size-1) {
result[i] = data[i].
length
}
result
}

addTwoNumbers(data, lambda)
}

fun addTwoNumbers(stringList : List<String>, lambdaResult : (List<String>) -> IntArray) {
var result = lambdaResult(stringList)
for(i in result) {
println(i)
}
}
Program to add 2 numbers using higher order function and lambda expression

fun main() {
val lambdaResult : (Int, Int) -> Int = { x, y -> x + y}
addTwoNumbers(3, 8, lambdaResult)
addTwoNumbers(3, 8, { x : Int, y : Int -> x + y})
}

fun addTwoNumbers(a : Int, b : Int, action : (Int, Int) -> Int) {
val result = action(a, b)
println(result)
}
A higher-order function is a function that takes functions as parameters or returns a function.
Eg : A function can take functions as parameters
fun passMeFunction(abc: () -> Unit) {
// I can take function
// do something here
// execute the function
abc()
}
Eg :  A function can return another function
fun add(a: Int, b: Int): Int {
return a + b
}

With and Apply

fun main() {

var person = Person()

with(person) {
name = "Keshav"
age = 35
}

person.apply {
name = "Keshav"
age = 35
}.startRun()

println(person.name)
println(person.age)
}

class Person {
var name : String = ""
var age : Int = -1

fun startRun() {
println("I am Ready")
}
}


Following are the differences between Kotlin and Java:-

Singleton :
object SomeSingleton
public final class SomeSingleton {
   public static final SomeSingleton INSTANCE;

   private SomeSingleton() {
      INSTANCE = (SomeSingleton)this;
      System.out.println("init complete");
   }

   static {
      new SomeSingleton();
   }
}

What is a Coroutine ?

  • A coroutine is a concurrency design pattern that we can use in Android to simplify code that executes asynchronously
  • Coroutines is a lightweight thread because creating coroutines doesn't allocate new threads. Instead, they use predefined thread pools, and smart scheduling. Scheduling is the process of determining which piece of work you will execute next.. Is called on suspending functions. We can suspend and resume the Coroutines while execution. This means we can have a long-running task, which can be executed one by one or  which can be executed little-by-little.. We can pause it any number of times and resume it when we are ready again..
  • If we need to call a coroutine inside a fragment then we can use lifecycle scope of coroutines ie. viewLifecycleOwner.lifecycleScope.launch {  //suspending function }

Why Coroutines ?

  • AsyncTask can easily introduce memory leaks in our app. 
  • Managing threads are another pain
  • To use reftrofit, we need to have min version of 2.6 cos retrofit started supporting coroutines from this version
How Coroutine is different from a thread?
  • Executed within a thread
  • Coroutines are suspendable
  • They can switch their context i.e. the coroutine started running in 1 thread can easily switch the thread it is running in.
GlobalScope.launch {}
  • This means that the coroutine will leave as long as the application runs. 
  • If the coroutine has finished the job then it gets destroyed. 
  • Will be started in a separate thread.
  • So whatever we write within the block will be executed asynchronously
  • We can use delay method which is a suspending function. It will block the current coroutine but will not block the whole Thread
  • If the main thread finishes its work, all other threads and coroutines will be cancelled
  • If we are calling 2 suspending functions from the same coroutine and if we are adding delay functions in both of them then it will delay for the total delays that were mentioned in the suspending functions
  • It will return a job
    val job = GlobalScope.launch(Dispatcher.Default ) {

    }
    runBlocking {
    job.join()
    job.cancel()
    }
    We can wait for the job to finish by calling  job.join() which is a suspending function and should be called from a coroutine
    Join() will block our thread until that particular coroutine is finished.
    If we don't call job.join() then it will not wait for the job to complete and the code next to it will get executed immediately
    val TAG = "MainActivity"
    val job = GlobalScope.launch(Dispatchers.IO) {
    val time = measureTimeMillis {
    var result1 : String? = null
    var result2 : String? = null
    val job1 = launch { result1 = networkCall1() }
    val job2 = launch { result2 = networkCall2() }
    job1.join()
    job2.join()
    Log.d(TAG, "Answer1 is $result1")
    Log.d(TAG, "Answer2 is $result2")
    }
    Log.d(TAG, "Time taken $time")
    }
    Here if we don't call job.join() then we will get null as the result as we will not wait for the coroutine to complete the execution
Cancelling a Coroutine : 
We can cancel a coroutine by calling job.cancel()
The isActive condition checks if the coroutine is cancelled before trying to execute the code with in its block
val TAG = "MainActivity"
val job = GlobalScope.launch(Dispatchers.Default) {
Log.d(TAG, "Starting calculation")
for(i in 10..20) {
if(isActive) {
Log.d(TAG, "Result for i = $i: ${fib(i)}")
}
}
Log.d(TAG, "Ending the calculation")
}

runBlocking {
delay(1000L)
job.join()
job.cancel()
}
}

fun fib(n : Int) : Long {
return if(n == 0) 0
else if(n == 1) 1
else fib(n-1) + fib(n-2)
}
If we have a network call that takes too much time and we want to cancel it. For this coroutines come with a useful suspend function called withTimeout(3000L)
So withTimeout() is actuallly equivalent to launching a coroutine and delaying it for 3 secs and cancelling the job. 
val TAG = "MainActivity"
val job = GlobalScope.launch(Dispatchers.Default) {
Log.d(TAG, "Starting calculation")
withTimeout(3000L) {
for(i in 10..20) {
if(isActive) {
Log.d(TAG, "Result for i = $i: ${fib(i)}")
}
}
}
Log.d(TAG, "Ending the calculation")
}
fun fib(n : Int) : Long {
return if(n == 0) 0
else if(n == 1) 1
else fib(n-1) + fib(n-2)
}

Different Types of Coroutine Scopes :
  • LifecycleScope : This makes sure that all the coroutines running in that particular activity/fragment are destroyed when that particular activity/fragment is destroyed
  • ViewModelScope : It will keep the coroutine alive as long as the ViewModel is alive 
  • GlobalScope : Lifecycle of this scope is equal to the lifecycle of the entire application and causes memory leaks. To be used only while debugging and not in production applications.  GlobalScope.launch { }
  • Use Dispatchers ie GlobalScope.launch(Dispatchers.Main) { } to make Main Safe
  • We should always create a scope which automatically gets destroyed whenever its not needed so that it wont cause any memory leak ie. viewLifecycleOwner.lifecycleScope.launch {  //suspending function }
  • Dispatchers.IO - Used for Network Request or Disk read write. USed for Data Operations like Networking, Database operations, Reading or writing to Files
  • Dispatchers.Main - For Main Safety, Starts the coroutine on the main thread.
    Useful if we have to do any UI operations within the coroutine.
  • Dispatchers.Default - CPU Intensive Tasks. Used if we have to do long running or complex calculations that may block the main thread
    Eg : If we have to sort a list of 10000 list of elements
Dispatchers help coroutines in deciding the thread on which the work has to be done. Dispatchers are passed as the arguments to the GlobalScope by mentioning which type of dispatchers we can use depending on the work that we want the coroutine to do.

If we have to do a network call and update the UI then we need to change the context of the coroutine. i.e. we need to use Dispatchers.IO to make the network call and Dispatchers.Main to update the UI based on the results of the network call.
GlobalScope.launch(Dispatchers.IO) {
    val result = doNetworkCall()
    withContext(Dispatchers.Main) {
        updateUI()
    }
}

RunBlocking :
RunBlocking actually blocks the main thread. whereas GlobalScope.launch(Dispatchers.Main) doesn't block the main thread.
Can be useful if we want to call a suspending function on the main thread and not use a coroutine to call it. It runs synchronously
Can be used to run Junit to access suspend function. Will be in sync with the main thread flow.
We can call the launch() within the runBlocking as well and it will run asynchronously

Async & Await
If we have several suspend functions and we have to execute them both in a coroutine then they are sequential by default which means 1st function will be executed first and when its finished, the second will be executed. If we need to call the 2 functions parallelly i.e both the functions need to be called simultaneously,  then we need to use Async call. 
The below code will execute and take 6 secs to execute and give the results and will execute sequentially
val TAG = "MainActivity"
val job = GlobalScope.launch(Dispatchers.IO) {
val time = measureTimeMillis {
val result1 = networkCall1()
val result2 = networkCall2()
Log.d(TAG, "Answer1 is $result1")
Log.d(TAG, "Answer2 is $result2")
}
Log.d(TAG, "Time taken $time")
}
suspend fun networkCall1() : String {
delay(3000L)
return "Result 1"
}

suspend fun networkCall2() : String {
delay(3000L)
return "Result 2"
}
The below code can be used to call the coroutines paralelly using launch operation but its a tideous job to do
val TAG = "MainActivity"
val job = GlobalScope.launch(Dispatchers.IO) {
val time = measureTimeMillis {
var result1 : String? = null
var result2 : String? = null
val job1 = launch { result1 = networkCall1() }
val job2 = launch { result2 = networkCall2() }
job1.join()
job2.join()
Log.d(TAG, "Answer1 is $result1")
Log.d(TAG, "Answer2 is $result2")
}
Log.d(TAG, "Time taken $time")
}
Using Async and Await:
The
async call will return a Deferred result. The deferred type depends on what the async block is returning. If we need to do something asynchronously and need the result of it then we need to make use of it and to get the result we need to call the await() method and this will block the coroutine until the result is available
val TAG = "MainActivity"
val job = GlobalScope.launch(Dispatchers.IO) {
val time = measureTimeMillis {
var result1 = async { networkCall1() }
var result2 = async { networkCall2() }
Log.d(TAG, "Answer1 is ${result1.await()}")
Log.d(TAG, "Answer2 is ${result2.await()}")
}
Log.d(TAG, "Time taken $time")
}

Suspending Functions :

A function that may be started, halted, then resumed is known as a suspend function. One of the most important things to remember about the suspend functions is that they can only be invoked from another suspend function or from a coroutine. Suspending functions are merely standard Kotlin functions with the suspend modifier added, indicating that they can suspend coroutine execution without blocking the current thread. This means that the code you're looking at may pause execution when it calls a suspending function and restart execution at a later time.

Suspending functions can call any other ordinary functions, but another suspending function is required to suspend the execution. Because a suspending function cannot be called from a regular function, numerous coroutine builders are supplied, allowing you to call a suspending function from a non-suspending scope like launch, async, or runBlocking.
delay() function is an example of suspend function
runBlocking {
    delay(1000)
}

Why launch is called fire and forget ??

The launch coroutine builder does a fire and forget, it does not return any result to the caller asides the job instance which is just a handle to the background operation. 

It inherits the context and job from the scope where it was called but these can be overridden

Fire and forget means it won't return the result to the caller. The coroutineScope builder will suspend itself until all coroutines started inside of it are complete

Differentiate between launch / join and async / await

launch / join:-
The launch command is used to start and stop a coroutine. It's as though a new thread has been started. If the code inside the launch throws an exception, it's considered as an uncaught exception in a thread, which is typically written to stderr in backend JVM programs and crashes Android applications. Join is used to wait for the launched coroutine to complete before propagating its exception. A crashed child coroutine, on the other hand, cancels its parent with the matching exception.

async / await:-
The async keyword is used to initiate a coroutine that computes a result. We must use await on the result, which is represented by an instance of Deferred. Uncaught exceptions in async code are held in the resultant Deferred and are not transmitted anywhere else. They are not executed until processed

Difference between launch and async

The launch{} does not return anything and the async{} returns an instance of Deferred, which has an await() function. In other words, we can say that launch is used to fire and forget, and async is used to perform a task and return a result.

1. Async returns a value which is a Deferred which has the value and to get the value from the Deferred, we need to use AWAIT on it and this is when we get the actual value
eg : 
viewModelScope.launch {
    students as MutableLiveData
    val students = async {
        getStudents() 
    }
    val data = students.await() // Here the data will have the List of Students
}

1. Launch doesn't return a value. The method launch follows is Fire and Forget
eg : 
viewModelScope.launch {
    students as MutableLiveData
    students.value = getStudents()
}

2. Launch executes sequentially
eg : 
    viewModelScope.launch {
    getStudents()
    getStudents()
    
    getColleges() 
    getColleges() 
}

2. Async executes parallelly
eg : 
viewModelScope.launch {
    val students1 = async { getStudents() }
    val students2 = async { getStudents() }

    val colleges1 = async { getColleges() }
    val colleges2 = async { getColleges() }
}

launch is used to fire and forget coroutine. It is like starting a new thread. If the code inside the launch terminates with exception, then it is treated like uncaught exception in a thread -- usually printed to stderr in backend JVM applications and crashes Android applications. 

join is used to wait for completion of the launched coroutine and it does not propagate its exception. However, a crashed child coroutine cancels its parent with the corresponding exception, too.

async is used to start a coroutine that computes some result. The result is represented by an instance of Deferred and you must use await on it. 

An uncaught exception inside the async code is stored inside the resulting Deferred and is not delivered anywhere else, it will get silently dropped unless processed. You MUST NOT forget about the coroutine you’ve started with async.

What are Coroutine Builders?

Coroutine builders are a way of creating coroutines. Since they are not suspending themselves, they can be called from non-suspending code or any other piece of code. They act as a link between the suspending and non-suspending parts of our code.

The following are three coroutine builders:

1. runBlocking

2. launch

3. async

RunBlocking : 

runBlocking is a coroutine builder that blocks the current thread until all tasks of the coroutine it
creates, finish. So, why do we need runBlocking when we are clearly focusing to avoid blocking
the main thread? 

We typically runBlocking to run tests on suspending functions. While running tests, we want to
make sure not to finish the test while we are doing heavy work in test suspend functions.

Launch :

launch is essentially a Kotlin coroutine builder that is “fire and forget”. This means that launch
creates a new coroutine that won’t return any result to the caller. It also allows to start a
coroutine in the background.
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {
println("Starting main function..")

GlobalScope.launch {
println(doSomething())
}
runBlocking {
delay(4000L) // make sure to keep the JVM alive in order to wait for doSomething() to execute
}
}

suspend fun doSomething() : String {
delay(3000L) // simulate long running task
return "Did something that was 3 seconds long"
}

In the above code, we call the suspending function, doSomething() and after 3 seconds of delay we print the string: “Did something that is 3 seconds long”.

Note: “delay” in Kotlin, is a suspend function which delays a coroutine without blocking the current thread. It then resumes the coroutine after the specified time (In our case, 3 seconds or 3000 milliseconds).

Async-Await()
Async is a coroutine builder which returns some value to the caller. async can be used to perform an asynchronous task which returns a value. This value in Kotlin terms is a Deferred<T> value.
We can simply use async to create a coroutine doing some heavy work
await() is a suspending function that is called upon the async builder to fetch the value of the Deferred object that is returned. The coroutine started by async will be suspended until the result is ready. When the result is ready, it is returned and the coroutine resumes.
import kotlinx.coroutines.*

val a = 10
val b = 20

fun main() {
println("Gone to calculate sum of a & b")

GlobalScope.launch {
val result = async {
calculateSum()
}
println("Sum of a & b is: ${result.await()}")
}
println("Carry on with some other task while the coroutine is waiting for a result...")
runBlocking {
delay(3000L) // keeping jvm alive till calculateSum is finished
}
}

suspend fun calculateSum(): Int {
delay(2000L) // simulate long running task
return a + b
}



Advantages/Properties :

  • Run 1 background thread & Multiple Coroutines
  • Light Weight Threads
  • Can run in parallel
  • Can wait for each other
  • Communicate with each other
  • Coroutines != thread
  • Threads doesn't have such inbuilt mechanism to cancel itself internally
  • Can create 100's of coroutines without any memory issue
  • For threads, application waits for the background thread to complete its execution whereas in case of Coroutines the application will not have any idea about coroutines
  • If we are calling any coroutine from a fragment or activity using lifecycleScope.launch(), whenever the activity or fragment is destroyed, the coroutine also gets destroyed

How to make use of Coroutine Scope ?

To make use of Dispatchers.IO within the CoroutineScope ie Network Requests, we need to call as
withContext(Dispatchers.IO) {
    MyApi().getMovies().body()?.quotes
}

Recommended Part : 
viewLifecycleOwner.lifecycleScope.launch {  //suspending function }

Not a recommended Part.
Whenever we need to define a coroutine scope, we should implement CoroutineScope interface and override coroutineContext
eg : class ABC : CoroutineScope {

        private var job: Job()

        override val coroutineContext : CoroutineContext
            get() = job + Dispatchers.Main    // To make the application Main Safe,Job ->Coroutine Context

        onCreate() {
            job = Job()
        }

        onDestroy() {
            job.cancel()
        }

}

Creating a Coroutine : 

GlobalScope.launch {

// Tasks to be executed

}

This doesn't block the main thread, Also its a companion object

Coroutine Builders : 

Launch, Async & RunBlocking

launch(local scope) : 

Login Operation or logic computation

viewModelScope.launch {

}

GlobalScope.launch{}(Global/Application Level)(Like file download, play music which happens in background) 

Not much preferred cos if we miss the reference to it then it will be a problem as it will run in the background and consumes a lot of memory

async(local scope) :

GlobalScope.async{}(Global/Application Level)

runBlocking : 

runBlocking{}

Global coroutines are top level coroutines which can survive the entire life of the application

If the runBlocking is running in the main thread or thread T1 then the launch also runs on the same thread and acts as the child coroutine

i.e. it inherits the property of the immediate coroutine and returns a job object

Job Object : 

Used to control the coroutine

Launch creates a coroutine that runs on a thread

On the same thread there could be many coroutines that run on the same thread

launch coroutine builder is like fire and forget

when delay function is executed, it suspends the current coroutine and the thread remains free for the time period and can be utilized by other coroutine to run on this thread

It doesn't block the thread on which it operates

It inherits the thread and coroutine scope of the immediate parent coroutine

Returns the job object 

controls the coroutine

Async Coroutine Builders

Async doesnt return a job object. It returns a Deffered object which is a Generic type which is a subclass of job object

If we are not returning anything then we can go with deferred.join itself and if we are returning anything then we need to use deferred.await()

val data = deferred.await()

join() and await() are suspending functions. Also the delay function

The main difference between launch and async is that we can return some data and retrieve the same using await method

Async Coroutine Builders launches  new coroutine without blocking the current thread and inherits the thread and coroutine scope from immediate parent coroutine

returns a reference to Deferred<T> object

Using deferred object we can either cancel a coroutine or wait for coroutine to finish or retrieve the returned result

runBlocking is generally used to test the suspending functions. It blocks the current thread in which it is operating

Launch and Async coroutines never blocks the thread in which its running

Cancellation of Coroutine

When to cancel a coroutine : 

When the result is no longer required or if the coroutine is taking too longer to respond

To cancel a coroutine, it should be cooperative


val job = launch {

// The code has to be cooperative in order to get cancelled

}

job.cancel() // If the coroutine is cooperative then cancel it

job.join()  // Waits for the coroutine to get cancelled

job.join() will be effective only when the job.cancel() fails to cancel the coroutine

Always cancel and join work in conjunction with each other

job.cancelAndJoin() //if the coroutine is cooperative then cancel it else if it is not cooperative,  wait for the coroutine to finish

What makes the coroutine cooperative?

There are 2 ways to keep it cooperative

1. By periodically invoking the suspending function that checks for cancellation

Only those suspending function that belong to kotlinx.coroutines package will make coroutine cooperative and cancellable

functions like delay(). yield(), withContext(), withTimeout() etc.. are the functions that belongs to kotlinx.coroutines package

yield() - Faster than delay function. Doesn't delay out coroutine

2. Explicitly check for the cancellation status within the coroutine

CoroutineScope.isActive boolean flag

When its active, it returns true

If cancelled, it returns false

Handling Exceptions

  1. Cancellable suspending functions such as delay(), yield() etc throw CancellationException on coroutine cancellation which can be handled using Try & Catch block
  2. We cannot execute a suspending function from the finally block because the coroutine running this code is already cancelled
  3. If we want to execute a suspending function in finally block then we neeed to wrap the code within withContext(NonCancellable)function (NonCancellable is the companion object. We generally wont use suspending functions within the finally block. If required to write the suspending function within finally block then we need to wrap it within WithContext menthod
  4. We can print our own cancellation message using job.cancel(CancellationException("My Msg"))

eg : 

val job : Job = launch(Dispatchers.Default) {

try {

} catch(ex : CancellationException) {

print("Exception Caught Safely: ${ex.message}") // Prints "My Custom Message"

} finally {

withContext(NonCancellable) {

}

}

job.cancel(CancellationException("My Custom Message"))

jb.join() 

}

Timeouts :

1. withTimeout

2. withTimeoutOrNull

Both are coroutine builders

1. withTimeout :

  • TimeOutCancellationException is a subclass of CancellationException
  • The difference between withTimeout and withTimeoutOrNull is that it doesn't throw any exception. Hence it doesn't make any sense of writing Try Catch block.

2. withTimeoutOrNull returns some value from coroutine in the form of lambda result.

val result : String? = withTimeoutOrNull(2000) {
    "I am done"
}
print("Result: $result")

If there is a timeout i.e. if coroutine is unable to finish the task within the timeout, then we will get null result

Summary :

Cooperative Coroutines :

1. Use suspending functions i.e. yield(), delay()

2. Use CoroutineScope.isActive boolean flag

Handle Exceptions :

1. Handle Cancellationexception using try catch and finaly block

2. use withContext(NonCancellable) to execute suspending function withon finally

Timeouts :

1. withTimeOut : Handle TimeOutCancellationException

2. withTimeoutOrNull

Synthetic Extensions of Kotlin

plugins {
id 'kotlin-android'
id 'kotlin-android-extensions'
}


Comments

Popular posts from this blog

Android - Using KeyStore to encrypt and decrypt the data

Stack and Queue

Java Reflection API