Kotlin: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 974: Line 974:
                 .subscribe(consumer)
                 .subscribe(consumer)


</syntaxhighlight>
Add the plug in the root gradle and add it to the subprojects
=Install ktlint and Plugin=
<syntaxhighlight lang="gradle">
...
plugins {
  id("org.jlleitschuh.gradle.ktlint-idea") version "<current_version>"
}
...
allprojects {
    repositories {
...
    }
    apply plugin: "org.jlleitschuh.gradle.ktlint"
}
</syntaxhighlight>
You can not check if the task exist in gradle with
<syntaxhighlight lang="bash">
./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.
...
</syntaxhighlight>
==Plugin==
The plugin can be found at https://github.com/jlleitschuh/ktlint-gradle
<br>
You can install it in the root gradle file with
<syntaxhighlight lang="gradle">
plugins {
  id("org.jlleitschuh.gradle.ktlint-idea") version "<current_version>"
}
</syntaxhighlight>
==Running==
We can run this from the terminal with
<syntaxhighlight lang="bash">
./gradlew ktlintCheck
</syntaxhighlight>
==Fixing==
We can run this from the terminal with
<syntaxhighlight lang="bash">
./gradlew ktlintFormat
</syntaxhighlight>
</syntaxhighlight>

Revision as of 02:13, 19 December 2020

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")
}

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
        }
    }
}

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

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.
...

Plugin

The plugin can be found at https://github.com/jlleitschuh/ktlint-gradle
You can install it in the root gradle file with

plugins {
  id("org.jlleitschuh.gradle.ktlint-idea") version "<current_version>"
}

Running

We can run this from the terminal with

./gradlew ktlintCheck

Fixing

We can run this from the terminal with

./gradlew ktlintFormat