Kotlin: Difference between revisions
(107 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
== | =Introduction= | ||
data class | Kotlin is like java | ||
val | * JVM language | ||
val | * Object orientated | ||
val | * Functional language, high order functions, we can | ||
val | * store, pass and return functions | ||
var | <syntaxhighlight lang="kotlin"> | ||
) | fun main(args: Array<String>) { | ||
println("Hello World") | |||
} | |||
</syntaxhighlight> | |||
=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 | |||
<syntaxhighlight lang="bash"> | |||
# 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 | |||
</syntaxhighlight> | |||
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) | |||
<syntaxhighlight lang="bash"> | |||
mkdir scratch_kotlin2/src/main/kotlin/scratch_kotlin2/ | |||
mkdir scratch_kotlin2/mkdir gradle/wrapper | |||
</syntaxhighlight> | |||
Now make the gradle file. In the project root i.e. scratch_kotlin2/build.gradle | |||
<syntaxhighlight lang="groovy"> | |||
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' | |||
} | |||
</syntaxhighlight> | |||
Now create the launch file in the root i.e. scratch_kotlin2/.launch.json. '''Note''' the mainClass has dots and not slashes. | |||
<syntaxhighlight lang="json"> | |||
{ | |||
// 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" | |||
} | |||
] | |||
} | |||
</syntaxhighlight> | |||
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. | |||
<syntaxhighlight lang="txt"> | |||
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 | |||
</syntaxhighlight> | |||
Finally we make the app code in i.e. scratch_kotlin2/src/main/kotlin/scratch_kotlin2/App.kt | |||
<syntaxhighlight lang="kotlin"> | |||
package scratch_kotlin2 | |||
fun main(args: Array<String>) { | |||
println("Hello Gradle!") | |||
} | |||
</syntaxhighlight> | |||
==Gradle (Not used anymore - see above) == | |||
For Kotlin to work I had to amend gradle/wrapper/gradle-wrapper.properties to have | |||
<syntaxhighlight> | |||
#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 | |||
</syntaxhighlight> | |||
Change the version of Kotlin in to be 1.3.72 | |||
<syntaxhighlight lang="groovy"> | |||
.... | |||
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" | |||
} | |||
} | |||
.... | |||
</syntaxhighlight> | |||
==Maven== | |||
With Maven I had to make sure I had Maven installed and needed to change the pom.xml to use a different java. | |||
<syntaxhighlight lang="xml"> | |||
... | |||
<properties> | |||
<maven.compiler.source>15</maven.compiler.source> | |||
<maven.compiler.target>15</maven.compiler.target> | |||
</properties> | |||
... | |||
</syntaxhighlight> | |||
=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 | |||
<syntaxhighlight lang="kotlin"> | |||
val one = 1 // Int | |||
val threeBillion = 3000000000 // Long | |||
val oneLong = 1L // Long | |||
val oneByte: Byte = 1 | |||
</syntaxhighlight> | |||
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 | |||
<syntaxhighlight lang="kotlin"> | |||
val pi = 3.14 // Double | |||
val e = 2.7182818284 // Double | |||
val eFloat = 2.7182818284f // Float, actual value is 2.7182817 | |||
</syntaxhighlight> | |||
==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”. | |||
<syntaxhighlight lang="kotlin"> | |||
fun main(args: Array<String>) { | |||
val letter: Char // defining a variable | |||
letter = 'A' // Assigning a value to it | |||
println("$letter") | |||
} | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="kotlin"> | |||
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) | |||
} | |||
</syntaxhighlight> | |||
==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: | |||
<syntaxhighlight lang="kotlin"> | |||
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> | |||
// ... | |||
} | |||
</syntaxhighlight> | |||
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. | |||
<syntaxhighlight lang="kotlin"> | |||
fun main(args: Array<String>) { | |||
val numbers: IntArray = intArrayOf(1, 2, 3, 4, 5) | |||
println("Hey!! I am array Example"+numbers[2]) | |||
} | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="kotlin"> | |||
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 | |||
</syntaxhighlight> | |||
Here is an example reading bytes | |||
<syntaxhighlight lang="kotlin"> | |||
val myTest = 212 | |||
for(i in 0..7) | |||
{ | |||
val myGetValue = IsByteSet(myTest,i) | |||
val myTest2 = myGetValue | |||
} | |||
</syntaxhighlight> | |||
And a reading bits backwards | |||
<syntaxhighlight lang="kotlin"> | |||
val myTest = 212 | |||
for(i in 7 downTo 0) | |||
{ | |||
val myGetValue = IsByteSet(myTest,i) | |||
val myTest2 = myGetValue | |||
} | |||
</syntaxhighlight> | |||
=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 | |||
<syntaxhighlight lang="kotlin"> | |||
val ints = listOf(1,2,3,4,5) | |||
val smallInts = ints.filter{it < 4} | |||
// 1,2,3 | |||
</syntaxhighlight> | |||
===Map=== | |||
No any surprises here either | |||
<syntaxhighlight lang="kotlin"> | |||
val ints = listOf(1,2,3,4,5) | |||
val smallInts = ints.map{it * it} | |||
// 1,4,9,16, 25 | |||
</syntaxhighlight> | |||
===Flatmap=== | |||
This functions flattens a list of lists into a single list | |||
<syntaxhighlight lang="kotlin"> | |||
val meetings = listOf( | |||
Meeting(1, "Board"), | |||
Meeting(2, "Committee")) | |||
val people: List<Person> = meetings | |||
.flatMap(Meeting::people) | |||
// You could add distinct() to ensure distinction | |||
</syntaxhighlight> | |||
===Combining=== | |||
<syntaxhighlight lang="kotlin"> | |||
val ints = listOf(1,2,3,4,5) | |||
val smallInts = ints | |||
.filter(it < 5) | |||
.map{it * it} | |||
// 1,4,9,16 | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="kotlin"> | |||
val ints = listOf(1,2,3,4,5) | |||
var largeInts = ints.any{it > 3} | |||
var numberOfLargeInts = ints.count{it > 3} | |||
</syntaxhighlight> | |||
We can declare predicates as variables too. | |||
<syntaxhighlight lang="kotlin"> | |||
val greaterThanThree = {v:Int -> v > 3} | |||
var numberOfLargeInts = ints.count{greaterThanThree} | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="kotlin"> | |||
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) | |||
} | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="kotlin"> | |||
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) | |||
} | |||
</syntaxhighlight> | |||
=Flow Control= | |||
==If== | |||
if can be used as expression and statements. | |||
<syntaxhighlight lang="kotlin"> | |||
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" | |||
} | |||
</syntaxhighlight> | |||
==When== | |||
When can be used like a switch statement. | |||
<syntaxhighlight lang="kotlin"> | |||
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") | |||
} | |||
</syntaxhighlight> | |||
But it can also be used like an expression too. | |||
<syntaxhighlight lang="kotlin"> | |||
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\"") | |||
</syntaxhighlight> | |||
==Try== | |||
In kotlin you can use the try as an expression too. | |||
<syntaxhighlight lang="kotlin"> | |||
val result:Int = try { | |||
Integer.parseInt("ABC") | |||
} catch(e:NumberFormationException) { | |||
42 | |||
} | |||
</syntaxhighlight> | |||
==While== | |||
Same a java | |||
<syntaxhighlight lang="kotlin"> | |||
while (x > 0) { | |||
x-- | |||
} | |||
</syntaxhighlight> | |||
==Do== | |||
Same a java | |||
<syntaxhighlight lang="kotlin"> | |||
do { | |||
val y = retrieveData() | |||
} while (y != null) // y is visible here! | |||
</syntaxhighlight> | |||
==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 | |||
<syntaxhighlight lang="kotlin"> | |||
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}") | |||
} | |||
</syntaxhighlight> | |||
===Lists=== | |||
For lists we can deconstruct the list into an element and index | |||
<syntaxhighlight lang="kotlin"> | |||
var numbers = listOf(1,2,3,4,5) | |||
// blahhh blahhh | |||
for((index, element) in numbers.withIndex()) { | |||
print("$element at $index") | |||
} | |||
</syntaxhighlight> | |||
===Maps=== | |||
For maps we can deconstruct the map into a key and value | |||
<syntaxhighlight lang="kotlin"> | |||
var ages = TreeMap<String, Int>() | |||
// blahhh blahhh | |||
for((name, age) in ages) { | |||
print("$name is $age") | |||
} | |||
</syntaxhighlight> | |||
=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 | |||
<syntaxhighlight lang="kotlin"> | |||
fun test(inString : String) : Boolean { | |||
println("No alarms and no surprise $inString") | |||
return true | |||
} | |||
</syntaxhighlight> | |||
==Function Expressions== | |||
We can attach an expression to a function name. | |||
<syntaxhighlight lang="kotlin"> | |||
package rsk | |||
fun main(args: Array<String>) { | |||
println(max(1,2)) | |||
} | |||
fun max(a: Int, b: Int): Int = if(a >b) a else b | |||
</syntaxhighlight> | |||
==Calling From Java== | |||
To call from java we just need to add the package name to call the function | |||
<syntaxhighlight lang="kotlin"> | |||
fun main(args: Array<String>) { | |||
println(rsk.max(1,2)) | |||
} | |||
</syntaxhighlight> | |||
We can override this name in the kotlin package with | |||
<syntaxhighlight lang="kotlin"> | |||
@file:JvmName("DisplayFunctions") | |||
</syntaxhighlight> | |||
==Default Parameters== | |||
<syntaxhighlight lang="kotlin"> | |||
fun test(param1: String = "Test") { | |||
println("No alarms and no surprise $param1") | |||
} | |||
</syntaxhighlight> | |||
==Named Parameters== | |||
Nothing special here | |||
<syntaxhighlight lang="kotlin"> | |||
fun test(param1: String = "Test", param2: String = "Test") { | |||
println("No alarms and no surprise $param1") | |||
} | |||
test (param2 = "Test", param1 = "Test2") | |||
</syntaxhighlight> | |||
==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 | |||
<syntaxhighlight lang="kotlin"> | |||
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 | |||
</syntaxhighlight> | |||
==Infix Functions== | |||
This allows better looking code to achieve the same thing. To concatenate two fields we might do the following. | |||
<syntaxhighlight lang="kotlin"> | |||
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) | |||
</syntaxhighlight> | |||
By changing the prefix to have infix we can now write | |||
<syntaxhighlight lang="kotlin"> | |||
... | |||
infix fun Header.plus(other: Header): Header { | |||
return Header(this.Name + other.Name) | |||
} | |||
... | |||
val myH3 = myH1 plus myH2 | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="kotlin"> | |||
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) | |||
} | |||
</syntaxhighlight> | |||
==High-Order Functions== | |||
===Declarations and Calling=== | |||
To declare functions we put the parameters on the right and the return value on the left. | |||
<syntaxhighlight lang="kotlin"> | |||
val action: () -> Unit = { println("Hello, World") } | |||
val calc: (Int, Int) -> Int = { x,y -> x*y} | |||
fun doSomething(func: () -> Unit) { | |||
func() | |||
} | |||
fun main(...) { | |||
doSomething(action) | |||
} | |||
</syntaxhighlight> | |||
===Inlining Functions=== | |||
So we have | |||
<syntaxhighlight lang="kotlin"> | |||
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() | |||
} | |||
</syntaxhighlight> | |||
Let's decompile to see the java code using fernflower.jar | |||
<syntaxhighlight lang="bash"> | |||
java -jar fernflower.jar out/blah/blah/blah.class . | |||
</syntaxhighlight> | |||
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 | |||
<syntaxhighlight lang="kotlin"> | |||
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 | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
===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. | |||
<syntaxhighlight lang="kotlin"> | |||
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 | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
===Lambda Syntax=== | |||
Lambda Syntax is | |||
[[File:Kotlin lambda.png|400px]] | |||
<syntaxhighlight lang="kotlin"> | |||
//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 } | |||
</syntaxhighlight> | |||
<br> | |||
'''Predicates''' | |||
<br> | |||
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. | |||
<br> | |||
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. | |||
<syntaxhighlight lang="kotlin"> | |||
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 } | |||
</syntaxhighlight> | |||
Thinking of it like C# we can have a Select predicate | |||
<syntaxhighlight lang="c#"> | |||
int[] numbers = { 2, 3, 4, 5 }; | |||
var squaredNumbers = numbers.Select(x => x * x); | |||
</syntaxhighlight> | |||
===Quick Break Lambda=== | |||
Lambda are similar to the c# lambdas. Functions and be declared and called as below. | |||
<syntaxhighlight lang="kotlin"> | |||
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) | |||
</syntaxhighlight> | |||
===Implementiing Lambda=== | |||
So we can now use lambda with our code. | |||
<syntaxhighlight lang="kotlin"> | |||
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 | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
===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 | |||
<syntaxhighlight lang="kotlin"> | |||
fun main(args: Array<String>) { | |||
var total = 0; | |||
program.fibonacci(8) {it -> total += it) } | |||
println(total) | |||
} | |||
</syntaxhighlight> | |||
===With and Apply=== | |||
These are extension methods and allow the initialisation of objects. e.g. | |||
<syntaxhighlight lang="kotlin"> | |||
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() | |||
</syntaxhighlight> | |||
=Classes= | |||
==General== | |||
Defining classes is much like c++ and java | |||
<syntaxhighlight lang="kotlin"> | |||
class Person() { | |||
var Name: String = "" | |||
fun display() { | |||
println("Display :$Name" | |||
} | |||
} | |||
</syntaxhighlight> | |||
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. | |||
<syntaxhighlight lang="kotlin"> | |||
open class Person() { | |||
var Name: String = "" | |||
open fun display() { | |||
println("Display :$Name" | |||
} | |||
} | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="kotlin"> | |||
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) | |||
} | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="kotlin"> | |||
class Person(var Name: String, val IAmReadonlyLastName: String) { | |||
fun display() { | |||
println("Display :$Name $IAmReadonlyLastName" | |||
} | |||
} | |||
</syntaxhighlight> | |||
===Other Initialization=== | |||
We have initialise class properties also with the init function | |||
<syntaxhighlight lang="kotlin"> | |||
class Person() { | |||
val name: String | |||
fun init() { | |||
this.name = name | |||
this.last_name = last_name | |||
} | |||
} | |||
</syntaxhighlight> | |||
Or initialize using the incoming | |||
<syntaxhighlight lang="kotlin"> | |||
class Person(_name: String) { | |||
val name = _name | |||
} | |||
</syntaxhighlight> | |||
Secondary Constructors can be used. | |||
<syntaxhighlight lang="kotlin"> | |||
class Person(_name: String) { | |||
val name = _name | |||
val age = _age | |||
constructor(_name: String, _age: Int) { | |||
name = _name; | |||
age = _age; | |||
} | |||
} | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="kotlin"> | |||
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) {} | |||
</syntaxhighlight> | |||
==Lambda Functions== | |||
Kotlin supports lambda functions | |||
<syntaxhighlight lang="kotlin"> | |||
class Person(var Name: String) { | |||
fun display() { | |||
println("Display :$Name" | |||
} | |||
fun displayWithLambda(func: (s:String) -> Unit) { | |||
func(Name) | |||
} | |||
} | |||
</syntaxhighlight> | |||
==Generics== | |||
===Constraining Types=== | |||
When defining generics we can constrain the types they can use | |||
<syntaxhighlight lang="kotlin"> | |||
class Node<T : Number> (private val item:T) { | |||
fun value() :T { | |||
return item | |||
} | |||
} | |||
</syntaxhighlight> | |||
===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. | |||
<syntaxhighlight lang="kotlin"> | |||
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) { | |||
} | |||
} | |||
</syntaxhighlight> | |||
==Overriding== | |||
To override which interface to use if there are two, use the super keyword. This looks like something most unlikely but hey. | |||
<syntaxhighlight lang="kotlin"> | |||
interface A { fun doIt() = {} } | |||
interface B { fun doIt() = {} } | |||
class foo : A, B { | |||
override fun doIt() = { | |||
super<A>.doIt() | |||
} | |||
} | |||
</syntaxhighlight> | |||
=Companion Objects= | |||
==Singletons== | |||
With kotlin we can create object classes for singletons. E.g. | |||
<syntaxhighlight lang="kotlin"> | |||
object Meetings { | |||
var allMeetings = arrayListOf<Meeting>() | |||
} | |||
Meetings.allMeetings.add(Meeting(...)) | |||
</syntaxhighlight> | |||
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 | |||
<syntaxhighlight lang="kotlin"> | |||
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"); | |||
</syntaxhighlight> | |||
=Exceptions= | |||
==General== | |||
Kotlin, unlike old java does not require exceptions but you can use the standard approach. | |||
<syntaxhighlight lang="kotlin"> | |||
var reader = FileReader("Filename.txt") | |||
try { | |||
reader.read() | |||
} catch(e :IOException) { | |||
} finally { | |||
} | |||
</syntaxhighlight> | |||
=Other= | |||
==Nullable Stuff== | |||
===Let=== | |||
Let allows you to process non null checks. | |||
<syntaxhighlight lang="kotlin"> | |||
m?.Let { | |||
callFunctionWithNonNull) | |||
} | |||
</syntaxhighlight> | |||
===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. | |||
<syntaxhighlight lang="java"> | |||
public void addTitle(@NotNull String title) { | |||
this.title = title; | |||
} | |||
public @Nullable String meetingTitle() { | |||
return title; | |||
} | |||
</syntaxhighlight> | |||
==Reading and Writing to Gson== | ==Reading and Writing to Gson== | ||
Line 38: | Line 1,065: | ||
mySportsActivity2 = gson.fromJson(reader,SportsActivity::class.java) | 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 | |||
<syntaxhighlight lang="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' | |||
</syntaxhighlight> | |||
==Example usage== | |||
This example throttle button presses and is here just to remind ourselves on what RxJava looks like in Kotlin | |||
<syntaxhighlight lang="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() | |||
} | |||
</syntaxhighlight> | |||
==Example manual== | |||
To remind ourselves on what goes on under the hood we have use observers. | |||
<syntaxhighlight lang="kotlin"> | |||
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) | |||
} | |||
</syntaxhighlight> | |||
Overriding the four methods makes the code unneccesarily large and RxJava2 provides Comsumer<T> to reduce this. | |||
<syntaxhighlight lang="kotlin"> | |||
... | |||
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) | |||
</syntaxhighlight> | |||
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 | |||
<syntaxhighlight lang="kotlin"> | |||
Observable.just(1, 5, 10, 20).map { theNumber -> theNumber * 3 } | |||
.subscribe({ Log.i("map", it.toString()) }) | |||
</syntaxhighlight> | |||
==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. | |||
[[File:Map vs flatmap.png|700px]] | |||
==Subscribe On== | |||
To make sure we do not block the UI thread we can redirect the subscribe to the appropriate thread. | |||
<syntaxhighlight lang="kotlin"> | |||
networkService.getData() | |||
.subscribeOn(Schedulers.io()) | |||
.subscribe( | |||
{ Log.i("map", it.toString()) }) | |||
</syntaxhighlight> | |||
==ObserveOn== | |||
When we want to update a UI component we can used ObserveOn | |||
<syntaxhighlight lang="kotlin"> | |||
networkService.getData() | |||
.subscribeOn(Schedulers.io()) | |||
.observerOn(AndroidSchedulers.mainThread()) | |||
.subscribe( | |||
// Handle Result | |||
updateView() | |||
) | |||
</syntaxhighlight> | |||
Therefore we can be selective on where the code is run | |||
<syntaxhighlight lang="kotlin"> | |||
networkService.getData() | |||
.subscribeOn(Schedulers.io()) // io thread | |||
.observeOn(Schedulers.computation()) // change thread Comp | |||
.map { | |||
doSomeComputations() | |||
} | |||
.observeOn(AndroidSchedulers.mainThread()) // change thread UI | |||
.subscribe( | |||
updateView() | |||
) | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="kotlin"> | |||
interface SingleObserver<T> { | |||
void onSubscribe(Disposable d); | |||
void onSuccess(T value); | |||
void onError(Throwable error); | |||
} | |||
</syntaxhighlight> | |||
*Completable | |||
Completable is only concerned with execution completion whether the task has reach to completion or some error has occurred | |||
<syntaxhighlight lang="kotlin"> | |||
interface CompletableObserver<T> { | |||
void onSubscribe(Disposable d); | |||
void onComplete(); | |||
void onError(Throwable error); | |||
} | |||
</syntaxhighlight> | |||
*Maybe | |||
Maybe is similar to Single only difference being that it allows for no emissions as well. | |||
<syntaxhighlight lang="kotlin"> | |||
interface MaybeObserver<T> { | |||
void onSubscribe(Disposable d); | |||
void onSuccess(T value); | |||
void onError(Throwable error); | |||
void onComplete(); | |||
} | |||
</syntaxhighlight> | |||
==Disposing of Observables== | |||
We have to dispose of our observables. In the demo they made a base activity and this had a functions | |||
<syntaxhighlight lang="kotlin"> | |||
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() | |||
} | |||
</syntaxhighlight> | |||
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. | |||
<syntaxhighlight lang="kotlin"> | |||
interface MainActivityInterface { | |||
fun onCreate() {} | |||
fun onPause() {} | |||
fun onResume() {} | |||
fun onDestroy() {} | |||
} | |||
</syntaxhighlight> | |||
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. | |||
[[File:MVVM2.png|500px]] | |||
Using Androids ViewModel means that the data will survive throw the lifecycle of an activity. | |||
[[File:Android ViewModel.png|500px]] | |||
===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= | |||
<syntaxhighlight lang="groovy"> | |||
... | |||
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> | |||
==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> |
Latest revision as of 05:36, 10 January 2022
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
//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.
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.
Using Androids ViewModel means that the data will survive throw the lifecycle of an activity.
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