Last active
August 21, 2019 08:14
-
-
Save kevivforever/a02a63085eeb72bceb771b80c3654a21 to your computer and use it in GitHub Desktop.
All Kotlin Basics explained with examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
╔══════════╦═════════════════╦═══════════════╦═══════════════╗ | |
║ Function ║ type ║ Argument ║ Result ║ | |
╠══════════╬═════════════════╬═══════════════╬═══════════════╣ | |
║ also ║ scoping ║ it ║ it ║ | |
╚══════════╩═════════════════╩═══════════════╩═══════════════╝ | |
also expressions does some additional processing on the object it was invoked. | |
Unlike let, it returns the original object instead of any new return data. | |
data class Person(var n: String, var t : String) | |
var person = Person("Anupam", "Kotlin") | |
person.also { it.t = "Kotlin" } | |
println(person) | |
Use the also() function, if your block does not access its receiver parameter at all, | |
or if it does not mutate its receiver parameter. Don’t use also() if your block needs to return a different value. | |
class Book(author: Person) { | |
val author = author.also { | |
requireNotNull(it.age) | |
print(it.name) | |
} | |
} | |
val original = "abc" | |
// Wrong | |
// Same value is sent in the chain (printed answer is wrong) | |
original.also { | |
println("The original String is $it") // "abc" | |
it.reversed() // even if we evolve it, it is useless | |
}.also { | |
println("The reverse String is ${it}") // "abc" | |
it.length // even if we evolve it, it is useless | |
}.also { | |
println("The length of the String is ${it}") // "abc" | |
} | |
// Corrected for also (i.e. manipulate as original string | |
// Same value is sent in the chain | |
original.also { | |
println("The original String is $it") // "abc" | |
}.also { | |
println("The reverse String is ${it.reversed()}") // "cba" | |
}.also { | |
println("The length of the String is ${it.length}") // 3 | |
} | |
When both combine the chain, i.e. one evolve itself, one retain itself, it becomes something powerful e.g. below | |
// Normal approach | |
fun makeDir(path: String): File { | |
val result = File(path) | |
result.mkdirs() | |
return result | |
} | |
// Improved approach | |
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
lambda expressions can’t have a return statement. The return type is either inferred from the variable which stores | |
the lambda or from the last statement of the lambda body. Another missing thing is multiple return points you might | |
often need. | |
// 1 | |
private fun isValidArtist(artist: String?): Boolean { | |
// 2 | |
val artistValidator = fun(value: String?): Boolean { | |
// 3 | |
if (value.isNullOrEmpty()) { | |
showError(R.string.error_artist_empty) | |
return false | |
} | |
if (value.length < 2) { | |
showError(R.string.error_artist_invalid) | |
return false | |
} | |
return true } | |
// 4 | |
return artistValidator(artist) | |
} | |
// 5 | |
private fun isValidYear(year: String?): Boolean { | |
val yearValidator: (String?) -> Boolean = { !it.isNullOrEmpty() && it.toInt() in 1877..2019 } | |
return yearValidator(year) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
╔══════════╦═════════════════╦═══════════════╦═══════════════╗ | |
║ Function ║ type ║ Argument ║ Result ║ | |
╠══════════╬═════════════════╬═══════════════╬═══════════════╣ | |
║ apply ║ extension ║ this ║ this ║ | |
╚══════════╩═════════════════╩═══════════════╩═══════════════╝ | |
apply runs on the object reference | |
Use the apply() function if you are not accessing any functions of the receiver within your block, and also want to return the same receiver. | |
val peter = Person().apply { | |
// only access properties in apply block! | |
name = "Peter" | |
age = 18 | |
} | |
// Normal approach | |
fun createInstance(args: Bundle) : MyFragment { | |
val fragment = MyFragment() | |
fragment.arguments = args | |
return fragment | |
} | |
// Improved approach | |
fun createInstance(args: Bundle) | |
= MyFragment().apply { arguments = args } | |
Or we could also making unchained object creation chain-able. | |
// Normal approach | |
fun createIntent(intentData: String, intentAction: String): Intent { | |
val intent = Intent() | |
intent.action = intentAction | |
intent.data=Uri.parse(intentData) | |
return intent | |
} | |
// Improved approach, chaining | |
fun createIntent(intentData: String, intentAction: String) = | |
Intent().apply { action = intentAction } | |
.apply { data = Uri.parse(intentData) } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
A closure is a lambda that is able to read and modify the variables defined outside its scope. | |
It is also known as variable capture inside lambda. It minimizes code redundancy and improves the overall readability. | |
// 1 | |
private fun findPlaylistYearRange(songs: List<String>) { | |
// 2 | |
if (songs.isEmpty()) { | |
return | |
} | |
// 3 | |
var startYear = songs[0].split(",")[2].trim().toInt() | |
var endYear = startYear | |
// 4 | |
val findStartEndYear = { | |
songs.forEach { song -> | |
val songYear = song.split(",")[2].trim().toInt() | |
if (songYear > endYear) { | |
endYear = songYear | |
} else if (songYear < startYear) { | |
startYear = songYear | |
} | |
} | |
} | |
// 5 | |
findStartEndYear() | |
// 6 | |
text_view_total.text = "($startYear - $endYear) - Total: ${songs.count()}" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// In the above program, a or b instead of a.or(b), and a and b instead of a.and(b) is used. | |
// It was possible because these two functions support infix notation. | |
fun main(args: Array<String>) { | |
val a = true | |
val b = false | |
var result: Boolean | |
result = a or b // a.or(b) | |
println("result = $result") | |
result = a and b // a.and(b) | |
println("result = $result") | |
} | |
//You can make a function call in Kotlin using infix notation if the function | |
//- is a member function (or an extension function). | |
//- has only one single parameter. | |
//- is marked with infix keyword. | |
class Structure() { | |
infix fun createPyramid(rows: Int) { | |
var k = 0 | |
for (i in 1..rows) { | |
k = 0 | |
for (space in 1..rows-i) { | |
print(" ") | |
} | |
while (k != 2*i-1) { | |
print("* ") | |
++k | |
} | |
println() | |
} | |
} | |
} | |
fun main(args: Array<String>) { | |
val p = Structure() | |
p createPyramid 4 // p.createPyramid(4) | |
} | |
* | |
* * * | |
* * * * * | |
* * * * * * * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// fragment argument null check | |
val arrg1 = arguments?.getString(ARGUMENTS_PATH) | |
// use let when you have multiple calls to execute if the variable is not null | |
arrg1?.let { | |
//perform your actions | |
} | |
//There are times when you want to run operations on the class by accessing 'this' run | |
//instead of let. | |
var onStackChangeListener: ((List<FileInfo>) -> Unit)? = null | |
onStackChangeListener?.run { | |
invoke(files) | |
println("pushing, calling " + toString()) | |
} | |
//You can also use ? for safe casts. | |
//Here, you only assign onItemClickListener to context as long as the cast to OnItemClickListener succeeds. | |
onItemClickListener = context as? OnItemClickListener | |
// equals function receiver is String? is nullable. Thats why you dont need the extra null check. | |
// In fact, Kotlin takes null into account when you are testing for equality in general. The == operator will call the 'equals' | |
//function as long as the item on the left isn't null. | |
if (item == SHRED_MODE_RANDOM) { | |
} | |
// add safe call operators. If list is null, instead of returning null, | |
//you return an empty list, which still adheres to the contract | |
fun fileList(path: String, | |
showHiddenFiles: Boolean = false, | |
onlyFolders: Boolean = false): List<File> { | |
val file = File(path) | |
val list = file.listFiles() | |
?.filter { showHiddenFiles || !it.name.startsWith(".") } | |
?.filter { !onlyFolders || it.isDirectory } | |
?.toList() | |
return list ?: listOf() | |
} | |
//It’s sometimes acceptable to return null on an error, but using null to represent a | |
//state is generally problematic. Variables shouldn’t have hidden or double meanings. | |
//An example is an Int? that stores the number of logged-in users, unless it’s null, | |
//which then means the app is in maintenance mode. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val lambdaName : Type = { parameterList -> codeBody } | |
If a lambda has a function as its last or only parameter, you can omit the parentheses as you did in the previous code. | |
//example 1 | |
cancelImage.setOnClickListener(object : View.OnClickListener { //1 | |
override fun onClick(v: View?) { // 2 | |
dismiss() | |
} | |
}) | |
// lambda version | |
cancelImage.setOnClickListener { dismiss() } | |
//example 2 | |
// 1 | |
val lambda1: () -> Unit = { println("Hello, world") } | |
// 2 | |
val lambda2 = { println("Hello, world") } | |
// 3 | |
lambda2() | |
example 3 | |
// 1 | |
val lambda1: (String) -> Unit = { name: String -> println("Hello, $name") } | |
// 2 | |
val lambda2: (String) -> Unit = { name -> println("Hello, $name") } | |
// 3 | |
val lambda3: (String) -> Unit = { println("Hello, $it") } | |
// 4 | |
val lambda4: (String) -> Unit = { println("Hello, World!") } | |
// 5 | |
val lambda5: (Int, Int) -> Int = { x, y -> | |
print(x) | |
print(y) | |
x + y | |
} | |
// 6 | |
val lambda6 = {x: Int, y: Int -> x + y } | |
example 4 | |
// 1 | |
val toggleEmptyView = { show: Boolean -> | |
// 2 | |
group_empty.visibility = if (show) View.VISIBLE else View.GONE | |
button_add_song.visibility = if (show) View.GONE else View.VISIBLE | |
} | |
example 5 | |
Observable.just(1) | |
.flatMap(object : Function<Int, Observable<Int>> { | |
override fun apply(integer: Int): Observable<Int> { | |
return Observable.just(integer * 10) | |
} | |
}) | |
.flatMap(object : Function<Int, Observable<Int>> { | |
override fun apply(integer: Int): Observable<Int> { | |
return Observable.just(integer * 20) | |
} | |
}) | |
.subscribeOn(Schedulers.io()) | |
.observeOn(AndroidSchedulers.mainThread()) | |
.subscribe(object : Observer<Int> { | |
override fun onComplete() { | |
} | |
override fun onSubscribe(d: Disposable) { | |
} | |
override fun onNext(t: Int) { | |
Log.d("result", "" + t) | |
} | |
override fun onError(e: Throwable) { | |
e.printStackTrace() | |
} | |
}) | |
Observable.just(1) | |
.flatMap { | |
return@flatMap Observable.just(it*10) | |
}.flatMap { | |
return@flatMap Observable.just(it*20) | |
}.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) | |
.subscribe({ | |
//OnNext | |
Log.d("result", "" + it) | |
},{ | |
it.printStackTrace() | |
//on error | |
},{ | |
//on complete | |
}) | |
example 6 | |
fun updateDirty(dirty: Int, operation: (Int) -> Int): Int { | |
return operation(dirty) | |
} | |
dirty = updateDirty(dirty, waterfilter) | |
// use :: is calling named functions | |
dirty = updateDirty(dirty, ::feedFish) | |
// pass a new lambda | |
dirty = updateDirty(dirty, { dirty -> dirty + 50 }) | |
// both are same | |
dirty = updateDirty(dirty) {dirty -> | |
dirty + 50 | |
} | |
example 7 | |
someRequest { data, id -> | |
val response = data.response | |
print("Received response : $response for the id $id") | |
}//we underscored the "id" parameter as we're not using it | |
someRequest { data, _ -> | |
print("Received response : ${data.response}") | |
} | |
In case you have more than one argument, you cannot use the it, you have to name each argument differently, | |
but if you’re not using an argument you can underscore it, to mark it unused. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
╔══════════╦═════════════════╦═══════════════╦═══════════════╗ | |
║ Function ║ type ║ Argument ║ Result ║ | |
╠══════════╬═════════════════╬═══════════════╬═══════════════╣ | |
║ let ║ scoping ║ it ║ result ║ | |
╚══════════╩═════════════════╩═══════════════╩═══════════════╝ | |
calling block: (T) | |
takes the object 'it' is invoked upon as the parameter and returns the result of the lambda expression. | |
'it' keyword contains the copy of the property inside let. | |
'it' is passed as argument | |
var str = "Hello World" | |
str.let { println("$it!!") } | |
The last value from the let is returned as an argument as shown below. | |
var strLength = str.let { "$it function".length } | |
println("strLength is $strLength") //prints: strLength is 25 | |
Chaining let functions | |
var a = 1 | |
var b= 2 | |
a = a.let { it + 2 }.let { val i = it + b | |
i} | |
println(a) //5 | |
nesting let functions -> For nested let, we can’t use it keyword. We need to assign explicit names to it in both the let functions. | |
var x = "Anupam" | |
x.let { outer -> outer.let { inner -> print("Inner is $inner and outer is $outer") } } //Prints: Inner is Anupam and outer is Anupam | |
null checks using let | |
var name : String? = "Kotlin let null check" | |
name?.let { println(it) } //prints Kotlin let null check | |
name = null | |
name?.let { println(it) } //nothing happens | |
val result = str.let { | |
print(it) // Argument | |
42 // Block return value | |
} | |
val original = "abc" | |
// Evolve the value and send to the next chain | |
original.let { | |
println("The original String is $it") // "abc" | |
it.reversed() // evolve it as parameter to send to next let | |
}.let { | |
println("The reverse String is $it") // "cba" | |
it.length // can be evolve to other type | |
}.let { | |
println("The length of the String is $it") // 3 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//- creates singleton | |
//- Similar to objects of a normal class, you can call methods and access properties by using the . notation | |
//- Whereas a class describes structures that can be instantiated as and when desired and allows for as many instances | |
// as needed, an object instead represents a single static instance, and can never have any more or any less than this | |
// one instance. | |
//-objects can extend classes and implement interfaces. | |
object Test { | |
private var a: Int = 0 | |
var b: Int = 1 | |
fun makeMe12(): Int { | |
a = 12 | |
return a | |
} | |
} | |
fun main(args: Array<String>) { | |
val result: Int | |
result = Test.makeMe12() | |
println("b = ${Test.b}") | |
println("result = $result") | |
} | |
val obj = object : MouseAdapter() { | |
override fun mouseClicked(e: MouseEvent) { | |
// ... | |
} | |
override fun mouseEntered(e: MouseEvent) { | |
// ... | |
} | |
} | |
window.addMouseListener(object : MouseAdapter() { | |
override fun mouseClicked(e: MouseEvent) { | |
// ... | |
} | |
override fun mouseEntered(e: MouseEvent) { | |
// ... | |
} | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
null check | |
https://www.raywenderlich.com/436090-null-safety-tutorial-in-kotlin-best-practices | |
let, also, apply, with, run, | |
https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84 | |
http://beust.com/weblog/2015/10/30/exploring-the-kotlin-standard-library/ | |
https://proandroiddev.com/the-difference-between-kotlins-functions-let-apply-with-run-and-else-ca51a4c696b8 | |
https://medium.com/@fatihcoskun/kotlin-scoping-functions-apply-vs-with-let-also-run-816e4efb75f5 | |
https://www.journaldev.com/19467/kotlin-let-run-also-apply-with#kotlin-let-vs-also | |
https://medium.com/@ramtop/kotlin-scope-functions-c8c41f09615f | |
object | |
https://www.baeldung.com/kotlin-objects | |
https://www.programiz.com/kotlin-programming/object-singleton | |
generics in out | |
https://medium.com/@elye.project/in-and-out-type-variant-of-kotlin-587e4fa2944c | |
https://blog.kotlin-academy.com/kotlin-generics-variance-modifiers-36b82c7caa39 | |
lambda anonymous | |
https://www.raywenderlich.com/2268700-introduction-to-kotlin-lambdas-getting-started |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
╔══════════╦═════════════════╦═══════════════╦═══════════════╗ | |
║ Function ║ type ║ Argument ║ Result ║ | |
╠══════════╬═════════════════╬═══════════════╬═══════════════╣ | |
║ run ║ extension ║ this ║ result ║ | |
╚══════════╩═════════════════╩═══════════════╩═══════════════╝ | |
extension function | |
calling block: T.() | |
stringVariable?.run { | |
println("The length of this String is $length") | |
} | |
In programming, this could be omitted most of the time. Therefore in our example above, we could use $length in the println statement, instead of ${this.length} | |
I call this as sending in 'this' as argument. | |
The last value from the let is returned as an argument as shown below. | |
var strLength = str.run { val l = length } | |
println("strLength is $strLength") //prints: strLength is 25 | |
//using let with run | |
var p : String? = null | |
p?.let { println("p is $p") } ?: run { println("p was null. Setting default value to: ") | |
p = "Kotlin"} | |
println(p) | |
//Prints | |
//p was null. Setting default value to: | |
//Kotlin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass { | |
fun test() { | |
val str: String = "..." | |
val result = str.xxx { | |
print(this) // Receiver | |
print(it) // Argument | |
42 // Block return value | |
} | |
} | |
} | |
╔══════════╦═════════════════╦═══════════════╦═══════════════╗ | |
║ Function ║ Receiver (this) ║ Argument (it) ║ Result ║ | |
╠══════════╬═════════════════╬═══════════════╬═══════════════╣ | |
║ let ║ this@MyClass ║ String("...") ║ Int(42) ║ | |
║ run ║ String("...") ║ N\A ║ Int(42) ║ | |
║ run* ║ this@MyClass ║ N\A ║ Int(42) ║ | |
║ with* ║ String("...") ║ N\A ║ Int(42) ║ | |
║ apply ║ String("...") ║ N\A ║ String("...") ║ | |
║ also ║ this@MyClass ║ String("...") ║ String("...") ║ | |
╚══════════╩═════════════════╩═══════════════╩═══════════════╝ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
╔══════════╦═════════════════╦═══════════════╦═══════════════╗ | |
║ Function ║ type ║ Argument ║ Result ║ | |
╠══════════╬═════════════════╬═══════════════╬═══════════════╣ | |
║ with* ║ normal ║ this ║ this ║ | |
╚══════════╩═════════════════╩═══════════════╩═══════════════╝ | |
normal function | |
Like 'apply', 'with' is used to change instance properties without the need to call dot operator over the reference every time. | |
with() is convenient when you find yourself having to call multiple different methods on the same object. | |
Instead of repeating the variable containing this object on each line, you can instead “factor it out” with a with call: | |
'with' just passes it as an argument | |
val w = Window() | |
with(w) { | |
setWidth(100) | |
setHeight(200) | |
setBackground(RED) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment