Kotlin

From bibbleWiki
Jump to navigation Jump to search

Introduction

Kotlin is like java

  • JVM language
  • Object orientated
  • Functional language, high order functions, we can
  • store, pass and return functions
fun main(args: Array<String>) {
    println("Hello World")
}

VS Code, Maven and Gradle

Debugging

Struggled every time to get the debugger to work. Using gradle init did not work for debugging but did work for build and run. So here is a full example built from scratch. First the environment

# Ubuntu 
lsb_release
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 21.10
Release:	21.10
Codename:	impish

# Kotlin in snap
kotlin             1.6.0                       63     latest/stable    jetbrains✓         classic

# Java 
java -version
openjdk version "11.0.13" 2021-10-19
OpenJDK Runtime Environment (build 11.0.13+8-Ubuntu-0ubuntu1.21.10)
OpenJDK 64-Bit Server VM (build 11.0.13+8-Ubuntu-0ubuntu1.21.10, mixed mode, sharing)

JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64

# Gradle version
gradle --version

------------------------------------------------------------
Gradle 7.1.1
------------------------------------------------------------

Build time:   2021-07-02 12:16:43 UTC
Revision:     774525a055494e0ece39f522ac7ad17498ce032c

Kotlin:       1.4.31
Groovy:       3.0.7
Ant:          Apache Ant(TM) version 1.10.9 compiled on September 27 2020
JVM:          11.0.13 (Ubuntu 11.0.13+8-Ubuntu-0ubuntu1.21.10)
OS:           Linux 5.13.0-23-generic amd64

So deliberately used a directory to demonstrate it working with a directory. I created a project scratch_kotlin2. The second directory is for the gradle override. (See below)

mkdir scratch_kotlin2/src/main/kotlin/scratch_kotlin2/
mkdir scratch_kotlin2/mkdir gradle/wrapper

Now make the gradle file. In the project root i.e. scratch_kotlin2/build.gradle

plugins {
    // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
    id 'org.jetbrains.kotlin.jvm' version '1.6.0'

    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

application {
    // Define the main class for the application.
    mainClass = 'scratch_kotlin2.AppKt'
}

Now create the launch file in the root i.e. scratch_kotlin2/.launch.json. Note the mainClass has dots and not slashes.

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "kotlin",
            "request": "launch",
            "name": "Kotlin Launch",
            "projectRoot": "${workspaceFolder}",
            "mainClass": "scratch_kotlin2.AppKt"
        }
    ]
}

Now let force VS Code to use our gradle. In gradle/wrapper created above create a gradle-wrapper.properties and set the version to be the one you are using.

distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

Finally we make the app code in i.e. scratch_kotlin2/src/main/kotlin/scratch_kotlin2/App.kt

package scratch_kotlin2

fun main(args: Array<String>) {
    println("Hello Gradle!")
}

Gradle (Not used anymore - see above)

For Kotlin to work I had to amend gradle/wrapper/gradle-wrapper.properties to have

#Sat Apr 14 11:41:07 BST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

Change the version of Kotlin in to be 1.3.72

....
buildscript {
    ext.kotlin_version = '1.3.72'
    ext.kotlin_coroutines_version = '0.19.3'

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
....

Maven

With Maven I had to make sure I had Maven installed and needed to change the pom.xml to use a different java.

...
    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>
...

Types

The following data type are available.

  • Numbers
  • Characters
  • Boolean
  • Strings
  • Arrays
  • Collections
  • Ranges

Numbers

The following Number type are available

  • Byte Size 8, min -128, max 127
  • Short Size 16, min -32768, max 32767
  • int Size 32, min -2,147,483,648 max 2,147,483,647
  • Long Size 64, min -9,223,372,036,854,775,808, max 9,223,372,036,854,775,807
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1

For Floating Point Numbers

  • Float Size 32, Significant bit 24, Exponent Bits 8, Decimal Digits 6-7
  • Double Size 64, Significant bit 53, Exponent Bits 11, Decimal Digits 15-16
val pi = 3.14 // Double
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, actual value is 2.7182817

Characters

Kotlin represents character using char. Character should be declared in a single quote like ‘c’. Please enter the following code in our coding ground and see how Kotlin interprets the character variable. Character variable cannot be declared like number variables. Kotlin variable can be declared in two ways - one using “var” and another using “val”.

fun main(args: Array<String>) {
   val letter: Char    // defining a variable 
   letter = 'A'        // Assigning a value to it 
   println("$letter")
}

Boolean

Two values true or false. (See Oracle for why two values was mentioned)

Strings

Strings are character arrays. Like Java, they are immutable in nature. We have two kinds of string available in Kotlin - one is called raw String and another is called escaped String. In the following example, we will make use of these strings.

fun main(args: Array<String>) {
   var rawString :String  = "I am Raw String!"
   val escapedString : String  = "I am escaped String!\n"
   
   println("Hello!"+escapedString)
   println("Hey!!"+rawString)   
}

Arrays

Arrays in Kotlin are represented by the Array class, that has get and set functions (that turn into [] by operator overloading conventions), and size property, along with a few other useful member functions:

class Array<T> private constructor() {
    val size: Int
    operator fun get(index: Int): T
    operator fun set(index: Int, value: T): Unit

    operator fun iterator(): Iterator<T>
    // ...
}

To create an array, we can use a library function arrayOf() and pass the item values to it, so that arrayOf(1, 2, 3) creates an array [1, 2, 3]. Alternatively, the arrayOfNulls() library function can be used to create an array of a given size filled with null elements.

fun main(args: Array<String>) {
   val numbers: IntArray = intArrayOf(1, 2, 3, 4, 5)
   println("Hey!! I am array Example"+numbers[2])
}

Ranges

Ranges is another unique characteristic of Kotlin. Like Haskell, it provides an operator that helps you iterate through a range. Internally, it is implemented using rangeTo() and its operator form is (..).

In the following example, we will see how Kotlin interprets this range operator.

fun main(args: Array<String>) {
   val i:Int  = 2
   for (j in 1..4) 
   print(j) // prints "1234"
   
   if (i in 1..10) { // equivalent of 1 < = i && i < = 10
      println("we found your number --"+i)
   }
}

// Output is 1234we found your number

Here is an example reading bytes

 val myTest = 212
 for(i in 0..7)
 {
   val myGetValue = IsByteSet(myTest,i)
   val myTest2 = myGetValue
 }

And a reading bits backwards

 val myTest = 212
 for(i in 7 downTo 0)
 {
   val myGetValue = IsByteSet(myTest,i)
   val myTest2 = myGetValue
 }

Collections

Filtering and Sorting

Kotlin provides filter, map and flat map

  • filter Similar to a where in SQL
  • map Similar to a select in SQL
  • flatmap See below (flattens maps)

Filter

No any surprises here

   val ints = listOf(1,2,3,4,5)

   val smallInts = ints.filter{it < 4}

// 1,2,3

Map

No any surprises here either

   val ints = listOf(1,2,3,4,5)

   val smallInts = ints.map{it * it}

// 1,4,9,16, 25

Flatmap

This functions flattens a list of lists into a single list

   val meetings = listOf(
        Meeting(1, "Board"), 
        Meeting(2, "Committee"))
   
   val people: List<Person> = meetings
       .flatMap(Meeting::people)

// You could add distinct() to ensure distinction

Combining

   val ints = listOf(1,2,3,4,5)

   val smallInts = ints
                     .filter(it < 5)
                     .map{it * it}

// 1,4,9,16

Predicates

Example predicates available in kotlin can be used on collections too.

  • all
  • any
  • count
  • find (returns first elements with match predicate or null)

Here are some examples again no surprises.

val ints = listOf(1,2,3,4,5)
var largeInts = ints.any{it > 3} 
var numberOfLargeInts = ints.count{it > 3}

We can declare predicates as variables too.

val greaterThanThree = {v:Int -> v > 3}

var numberOfLargeInts = ints.count{greaterThanThree}

Sequences

When we use sequences they are lazy evaluation. That means we can pass them around without great performance hits. I guess it is a bit like a yield keyword.

fun iter(seq: Sequence<String>) {
   for(t in seq) println(t)
}

fun main(args:Array<String>) {

    val titles: Sequence<String> = meetings
      .asSequence()
      .filter{...}
      .map{...}

    iter(titles)
}

Creating Collections

kotlin provides an extensive set of collections

  • Arrays, maps, sets and lists
  • Can be readonly

The two things to remember is that there are mutable and non mutable lists, nullable and not nullable lists. For iteration you can use the filterNotNull.

   var people : MutableList<Person?>? = null
    
   people = mutableListOf(Person(23), Person(24))
   
   people.add(null)

   // for(person: Person? in people) {
   for(person: Person in people.filterNotNull()) {
       println(person?.age)
   }

Flow Control

If

if can be used as expression and statements.

if(q.Answer == q.CorrectAnswer) {
 // Do something
}
else
{
 // Do something else
}

// Or we can do 
var message = if(q.Answer == q.CorrectAnswer) {
   "You were correct"
} else {
   "Try again"
}

When

When can be used like a switch statement.

when(number) {
    0 -> println("Invalid number")
    1, 2 -> println("Number too low")
    3 -> println("Number correct")
    4 -> println("Number too high, but acceptable")
    else -> println("Number too high")
}

But it can also be used like an expression too.

var result = when(number) {
    0 -> "Invalid number"
    1, 2 -> "Number too low"
    3 -> "Number correct"
    4 -> "Number too high, but acceptable"
    else -> "Number too high"
}
// it prints when returned "Number too low"
println("when returned \"$result\"")

Try

In kotlin you can use the try as an expression too.

val result:Int = try {
    Integer.parseInt("ABC")
} catch(e:NumberFormationException) {
    42
}

While

Same a java

while (x > 0) {
    x--
}

Do

Same a java

do {
    val y = retrieveData()
} while (y != null) // y is visible here!

For Loops

Basic

Kotlin does not support the for(var x=0; x<Total; x++). To do this in kotlin you need to use ranges

for(i in 1..10) {
    println("iain is ace")
}

// Or
for(i in 1..10 step 2) {
    println("iain is ace")
}

// Or
for(item in collection) {
    println("iain is ace {item.attribute1}")
}

Lists

For lists we can deconstruct the list into an element and index

var numbers = listOf(1,2,3,4,5)
// blahhh blahhh
for((index, element) in numbers.withIndex()) {
    print("$element at $index")
}

Maps

For maps we can deconstruct the map into a key and value

var ages = TreeMap<String, Int>() 
// blahhh blahhh
for((name, age) in ages) {
    print("$name is $age")
}

Functions

General

  • Kotlin Functions can be expressions
  • Functions can be called by Java
  • Can have default parameters
  • Can have named parameters
  • Can extend existing types
  • Infix functions
  • Tailrec functions
  • High-level functions

Here we go derrrrr

fun test(inString : String) : Boolean {
    println("No alarms and no surprise $inString")
    return true
}

Function Expressions

We can attach an expression to a function name.

package rsk

fun main(args: Array<String>) {
    println(max(1,2))
}

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

Calling From Java

To call from java we just need to add the package name to call the function

fun main(args: Array<String>) {
    println(rsk.max(1,2))
}

We can override this name in the kotlin package with

@file:JvmName("DisplayFunctions")

Default Parameters

fun test(param1: String = "Test") {
    println("No alarms and no surprise $param1")
}

Named Parameters

Nothing special here

fun test(param1: String = "Test", param2: String = "Test") {
    println("No alarms and no surprise $param1")
}
test (param2 = "Test", param1 = "Test2")

Extension Functions

This is like the approach for C#. This results in a static function but cuts down the use of utility code. These are static functions but reduce the number of utility functions. So in the example below we extend the String class

fun String.myOwnFunction(param1: String, param2: String): String {

    // this represents the receiver
    println("Iain is ace $param1, $param1, this")
}_
var myString = "This is a test"
myString.myOwnFunction("Ten", "Two")


println

Infix Functions

This allows better looking code to achieve the same thing. To concatenate two fields we might do the following.

class Header(var Name: String {}

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

var myH1 = new Header("H1")
var myH2 = new Header("H2")

val myH3 = myH1.plus(myH2)

By changing the prefix to have infix we can now write

...
infix fun Header.plus(other: Header): Header {
    return Header(this.Name + other.Name)
}
...
val myH3 = myH1 plus myH2

Tailrec Functions

I really liked this example. It demonstrates the fib series inside a recursive function. When looking for the 100,000 number a stack overflow occurs so you can see the point of marking recursive functions, where appropriate tailrec which turns it into a for loop under the covers.

fun main(args: Array<String>) {
   println(fib(100000, BigInteger("1"), BigInteger("0")))
}

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

High-Order Functions

Declarations and Calling

To declare functions we put the parameters on the right and the return value on the left.

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

fun doSomething(func: () -> Unit) {
   func()
}

fun main(...) {
   doSomething(action)
}

Inlining Functions

So we have

fun main(..) {
   val ints = listOf(1,2,3,4,5)
   val i = first(int, {i -> i == 3}
   
   println(i)
}

inline fun <T> first(items: List<T>, predicate: (T) -> Boolean) : T {
   for(item in items) {
      if(predicate(item)) return item
   }
   throw Exception()
}

Let's decompile to see the java code using fernflower.jar

java -jar fernflower.jar out/blah/blah/blah.class .

Looking at the class generated we see inline has created code inline with the main method. Very like c++ line.

Old Approach

To print the fibonacci sequence we could do

fun main(args: Array<String>) {
    var program = Program()
    program.fibonacci(8)
}

class Program {
    fun fibonacci(limit: Int) {
        var prev = 0
        var prevprev = 0
        var current = 1
    
        // 1,1,2,3,5,8,13
        for(i: Int in 1..limit) {
            println(current)

            var temp = current
            prevprev = prev
            prev = temp
            current = prev + prevprev
        }
    }
}

Separate Strategy

What we want to do with the result can be separated from the calculation itself by passing in the approach using anonymous functions.

fun main(args: Array<String>) {
    // var program = Program()
    program.fibonacci(8, object : Process {
        override fun execute(value: Int) {
            println(value)
        }
    })
}

interface Process {
    fun execute(value: Int)
}

class Program {
    fun fibonacci(limit: Int, action: Process) {
        var prev = 0
        var prevprev = 0
        var current = 1
    
        // 1,1,2,3,5,8,13
        for(i: Int in 1..limit) {
            // println(current)
            action.execute(current);

            var temp = current
            prevprev = prev
            prev = temp
            current = prev + prevprev
        }
    }
}

Lambda Syntax

Lambda Syntax is Kotlin lambda.png

//you can put the whole lambda inside the parentheses.
list.any(  { i: Int -> i > 0 }  )

// You can move lambda out of parentheses if the lambda is the last argument
list.any()   { i : Int -> i > 0}


// if the parentheses are empty you can even omit them.
list.any { i: Int -> i > 0 }

// If lambda has the only argument, you can replace its name with ‘it’.
list.any{ it > 0 }


Predicates
Filtering is one of the most popular tasks in the collection processing. In Kotlin, filtering conditions are defined by predicates– lambda functions that take a collection element and return a boolean value: true means that the given element matches the predicate, false means the opposite.
Some more examples. We can store the lambda in a variable. We use the any() which a predicate can take a lambda as an argument.

val isEven = { i: Int -> i % 2 ==0 }

val list = listOf(1,2,3,4)
list.any(isEven)  //true

// Or of course
list.any( { i: Int -> i % 2 ==0 } )  //true

// Or 
list.any { i: Int -> i % 2 == 0 }

Thinking of it like C# we can have a Select predicate

int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);

Quick Break Lambda

Lambda are similar to the c# lambdas. Functions and be declared and called as below.

fun calculateResult(a: Int, b: Int, func: (Int) -> Unit) {
    // a and b = some result
...
    func(result)
}

// This can be called either by
// My favourite
calculateResult(1,2, { s -> print(s) } )
calculateResult(1,2, { print(it) } )

// Their favourite 
calculateResult(1,2) { s -> print(s) }
calculateResult(1,2) { print(it) }

// The super short and should be my favourite 
calculateResult(1,2, ::print)

Implementiing Lambda

So we can now use lambda with our code.

fun main(args: Array<String>) {

    program.fibonacci(8, {n ->println(n)})

    // Or as "it" is the variable name
    program.fibonacci(8) { println(it) }

    // Or
    program.fibonacci(8, ::println)
}

class Program {
    fun fibonacci(limit: Int, action: (Int) -> Unit) {
        var prev = 0
        var prevprev = 0
        var current = 1
    
        // 1,1,2,3,5,8,13
        for(i: Int in 1..limit) {
            // println(current)
            action(current);

            var temp = current
            prevprev = prev
            prev = temp
            current = prev + prevprev
        }
    }
}

Closures

Closures are functions that can access and modify properties defined outside the scope of the function. To do this in java is quite difficult but this is easy in kotlin and demonstrating the point of separating concerns. We could total up the values in a fib sequence by doing

fun main(args: Array<String>) {
    var total = 0;
    program.fibonacci(8) {it -> total += it) }
    println(total)
}

With and Apply

These are extension methods and allow the initialisation of objects. e.g.

   val m = SomeClass()

   with(m) {
       SomeProp1 = "prop1"
       SomeProp2 = "prop2"
       SomeProp3 = "prop3"
   }

// Apply returns an object to allow chaining 

   m.apply {
       SomeProp1 = "prop1"
       SomeProp2 = "prop2"
       SomeProp3 = "prop3"
   }.nextFunctionCall()

Classes

General

Defining classes is much like c++ and java

class Person() {
    var Name: String = ""

    fun display() {
        println("Display :$Name"
    }
}

They have the following charactistics

  • final by default.
  • abstract classes
  • Sealed classes
  • Constructors
  • Data Classes
  • Lambda Functions

Final by Default

The classes cannot be derived from by default. There functions final by default too. To override you need to specify the open keyword.

open class Person() {
    var Name: String = ""

    open fun display() {
        println("Display :$Name"
    }
}

Abstract Classes

This forces the user, as it does in java, c# and c++ to derive a class before it can be instantiated. Any functions which are abstract must also be implemented.

Sealed classes

Sealed classes allow you restrict class hierarchies.

sealed class Event {
    class SendEvent(id: Int, to: String) : Event()
    class ReceiveEvent(id: Int, to: String) : Event()
} 

// This can subsequently be used with a when
fun handEvent(e: Event) = 
   when(e) {
      is SendEvent -> print(e.to)
      is ReceiveEvent -> print(e.from)
   }

Constuctors

General

If you pass a var to a constructor the name is then associated with the class without declaring the value separately. You can also use val for readonly property.

class Person(var Name: String, val IAmReadonlyLastName: String) {

    fun display() {
        println("Display :$Name $IAmReadonlyLastName"
    }
}

Other Initialization

We have initialise class properties also with the init function

class Person() {

    val name: String

    fun init() {
        this.name = name
        this.last_name = last_name
    }
}

Or initialize using the incoming

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

Secondary Constructors can be used.

class Person(_name: String) {

    val name = _name
    val age = _age

    constructor(_name: String, _age: Int) {
        name = _name;
        age = _age;
    }
}

Data Classes

Using dataclass you get a hashcode method, a equals() method, a toString() method and a copy method. When a data class the objects are compared by value.

fun main(args: Array<String>) {
    var obj1 = Test(1,"Test")
    var obj2 = Test(1,"Test")

    if(obj1 == obj2) // Fails if not a data class
}

data class Test(val prop1: Int, val prop2: String) {}

Lambda Functions

Kotlin supports lambda functions

class Person(var Name: String) {

    fun display() {
        println("Display :$Name"
    }
  
    fun displayWithLambda(func: (s:String) -> Unit) {
        func(Name)
    }
}

Generics

Constraining Types

When defining generics we can constrain the types they can use

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

Generics At Runtime

  • Java erases all generic type information
  • Cannot check generic type at runtime

Using the keyword reified on line functions can allow you to know the types.

Intefaces

Like java, kotlin has interfaces

General

You can have default arguments and functions. The default access if public by default.

interface Time {
    fun setTime(hours: Int, mins: Int = 0, secs: Int = 0)
    fun setTime(time: IainTime) = setTime(time.hours)
}

class MyTime : Time {
   override fun setTime(hours: Int, mins: Int = 0, secs: Int = 0) {
   }
}

Overriding

To override which interface to use if there are two, use the super keyword. This looks like something most unlikely but hey.

interface A { fun doIt() = {} }
interface B { fun doIt() = {} }

class foo : A, B {
    override fun doIt() = {
        super<A>.doIt()
    }
}

Companion Objects

Singletons

With kotlin we can create object classes for singletons. E.g.

object Meetings {
    var allMeetings = arrayListOf<Meeting>()
}
Meetings.allMeetings.add(Meeting(...))

These are difficult to test so should be avoided where possible.

Companion Objects for Classes

Kotlin does not have static methods. To achieve the same as these you used companion objects. For example maybe add two factory methods for a class

class Student {
    // Interface is optional
    companion object : SomeInterface {
       override fun SomeInterface(item: Student) {
       }
       fun createType1Student(name: String) : Type1 {}
       fun createType2Student(name: String) : Type2 {}
    }
}

val myObject = Student.createType1Student("Fred");

Exceptions

General

Kotlin, unlike old java does not require exceptions but you can use the standard approach.

   var reader = FileReader("Filename.txt")
   try {
      reader.read()
   } catch(e :IOException) {

   } finally {

   }

Other

Nullable Stuff

Let

Let allows you to process non null checks.

m?.Let {
   callFunctionWithNonNull)
}

Lateinit

Late init allows you to state the variable is lateinit. This let you override the compiler and initialize later in the code. i.e. you can have a non null declared but not initialized

Java Nulls

In java we can add @Nullable to ensure that nulls are not passed between the two.

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

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

Reading and Writing to Gson

Given the following

    var mySportsActivity = SportsActivity(
           0.0,
           0.0,
           0,
           0.0,
           Date())
   val gson = Gson()
   val json = gson.toJson(mySportsActivity)
   var filename = "D:\\IAIN\\Output.json";

You can write to a file with

    FileWriter(filename).use {
       writer ->
       val gson = GsonBuilder().setPrettyPrinting().create()
       gson.toJson(mySportsActivity, writer)
    }

And read it back with

    FileReader("D:\\IAIN\\Output.json").use {
       reader ->
       val gson = GsonBuilder().setPrettyPrinting().create()
       mySportsActivity2 = gson.fromJson(reader,SportsActivity::class.java)
    }

Reactive Programming

Setting up RxJava2 in Android

We set up RxJava by add the following to the build.gradle

    //ReactiveX
    implementation 'com.jakewharton.rxbinding2:rxbinding-kotlin:2.2.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'

Example usage

This example throttle button presses and is here just to remind ourselves on what RxJava looks like in Kotlin

    private fun sugar() {
        println("sugar was ere")

        RxView.clicks(binding.buttonClickMe).map {
            incrementCounter1()
        }
            .throttleFirst(1000, TimeUnit.MILLISECONDS)
            .subscribe {
                  incrementCounter2()
            }
    }
    private fun incrementCounter1() {
        var newVal = binding.textviewCounter1.text.toString().toInt()
        newVal++
        binding.textviewCounter1.text =newVal.toString()
    }

    private fun incrementCounter2() {
        var newVal = binding.textviewCounter2.text.toString().toInt()
        newVal++
        binding.textviewCounter2.text =newVal.toString()
    }

Example manual

To remind ourselves on what goes on under the hood we have use observers.

    private fun noSugar() {
        val emmitter = PublishSubject.create<View>()
        binding.buttonClickMe.setOnClickListener(object : View.OnClickListener {
            override fun onClick(v: View) {
                emmitter.onNext(v)
            }
        })

        val observer = object : Observer<View> {
            override fun onSubscribe(d: Disposable) {
            }

            override fun onNext(t: View) {
                incrementCounter2()
            }

            override fun onError(e: Throwable) {
            }

            override fun onComplete() {
            }

        }
        emmitter.map(object : Function<View, View> {
            override fun apply(t: View): View {
                incrementCounter1()
                return t
            }
        })
                .throttleFirst(1000, TimeUnit.MILLISECONDS)
                .subscribe(observer)

    }

Overriding the four methods makes the code unneccesarily large and RxJava2 provides Comsumer<T> to reduce this.

...
        val consumer = Consumer<View> {
            incrementCounter2()
        }
...
        emmitter.map(object : Function<View, View> {
            override fun apply(t: View): View {
                incrementCounter1()
                return t
            }
        })
                .throttleFirst(1000, TimeUnit.MILLISECONDS)
                .subscribe(consumer)

Add the plug in the root gradle and add it to the subprojects

Main Operators And Observables

In the course these were regarded as the main operators

  • Subscribe
  • Map
  • FlatMap
  • Filter
  • SubscribeOn/ ObserveOn

These were the main observables

  • Observable
  • Single
  • Completable
  • Maybe

Testing RxJava2

Example for testing

        Observable.just(1, 5, 10, 20).map { theNumber -> theNumber * 3 }
            .subscribe({ Log.i("map", it.toString()) })

Map vs FlatMap

Map will take the input, execute your operation and wrap it back up. With FlatMap you can pass this on to another call, database or network and it will expect that to wrap it back up. Map vs flatmap.png

Subscribe On

To make sure we do not block the UI thread we can redirect the subscribe to the appropriate thread.

  networkService.getData()
        .subscribeOn(Schedulers.io())
        .subscribe(
            { Log.i("map", it.toString()) })

ObserveOn

When we want to update a UI component we can used ObserveOn

  networkService.getData()
        .subscribeOn(Schedulers.io())
        .observerOn(AndroidSchedulers.mainThread())
        .subscribe(
            // Handle Result
            updateView()
        )

Therefore we can be selective on where the code is run

        networkService.getData()
            .subscribeOn(Schedulers.io()) // io thread
            .observeOn(Schedulers.computation()) // change thread Comp
            .map {
                doSomeComputations()
            }
            .observeOn(AndroidSchedulers.mainThread()) // change thread UI
            .subscribe(
                updateView()
            )

Cut Down Observables

In a normal observable there is onSubscribe, onNext, onComplete and onError. RxJava provides others types which combine or drop some of these functions.

  • Single

Single is an Observable which only emits one item or throws an error. Single emits only one value and applying some of the operator makes no sense. Like we don’t want to take value and collect it to a list.

interface SingleObserver<T> {
    void onSubscribe(Disposable d);
    void onSuccess(T value);
    void onError(Throwable error);
}
  • Completable

Completable is only concerned with execution completion whether the task has reach to completion or some error has occurred

interface CompletableObserver<T> {
    void onSubscribe(Disposable d);
    void onComplete();    
    void onError(Throwable error);
}
  • Maybe

Maybe is similar to Single only difference being that it allows for no emissions as well.

interface MaybeObserver<T> {
    void onSubscribe(Disposable d);
    void onSuccess(T value);
    void onError(Throwable error);
    void onComplete();
}

Disposing of Observables

We have to dispose of our observables. In the demo they made a base activity and this had a functions

        private fun initRx() {
            disposables = CompositeDisposable()
        }
        @Synchronized protected fun addDisposable(disposable: CompositeDisposable?) {
            if(disposable == null) return
            disposable.add(disposable)
        }

        override fun onDestroy() {
            super.onDestroy()
            if(!disposables.isDisposed) disposables.dispose()
        }

So when using the observables you call the addDisposable

Design Patterns

We looked at

MVC Model, View, Controller

In MVC the controller (Controller holds the state). All of the logic ends up in the controller. Which means on the model can be tested separately. Requires onSaveInstanceState and onRestoreInstanceState() to manage rotation. Because the view and the controller are so tightly coupled unit testing is quite difficult.

MVP Model, View, Presenter

The Presenter contains the Business Logic and the State of the App. An interface is provided to communicate between the view and the presentor. e.g.

interface MainActivityInterface {
 fun onCreate() {}
 fun onPause() {}
 fun onResume() {}
 fun onDestroy() {}
}

The interface allow us to use mocks so unit testing is much easier.

MVvM Model,View, View Model

The model represent the state and data of the overall system, the View Model provides the state data for the specific instance of the app. MVVM2.png Using Androids ViewModel means that the data will survive throw the lifecycle of an activity. Android ViewModel.png

Separation of Concerns

View/Activity

  • User Input
  • Observe ViewModel
  • Reflect current State
  • Interact with system
  • Testable with instrumentation tests

ViewModel

  • Prepare data for View
  • Provide observable State
  • Business Logic
  • Coordination async calls
  • Testable with unit tests

Install ktlint and Plugin

...
plugins {
  id("org.jlleitschuh.gradle.ktlint-idea") version "<current_version>"
}
...
allprojects {
    repositories {
...
    }
    apply plugin: "org.jlleitschuh.gradle.ktlint"
}

You can not check if the task exist in gradle with

./gradlew tasks
...
Verification tasks
------------------
...
deviceCheck - Runs all device checks using Device Providers and Test Servers.
ktlintCheck - Runs ktlint on all kotlin sources in this project.
ktlintDebugCheck - Runs ktlint on all kotlin sources for android debug variant in this project.
ktlintReleaseCheck - Runs ktlint on all kotlin sources for android release variant in this project.
...
lint - Runs lint on all variants.
lintDebug - Runs lint on the Debug build.
lintRelease - Runs lint on the Release build.
lintVitalRelease - Runs lint on just the fatal issues in the release build.
test - Run unit tests for all variants.
...

Running

We can run this from the terminal with

./gradlew ktlintCheck

Fixing

We can run this from the terminal with

./gradlew ktlintFormat