Skip to content

Instantly share code, notes, and snippets.

@kevivforever
Last active August 21, 2019 08:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kevivforever/a02a63085eeb72bceb771b80c3654a21 to your computer and use it in GitHub Desktop.
Save kevivforever/a02a63085eeb72bceb771b80c3654a21 to your computer and use it in GitHub Desktop.
All Kotlin Basics explained with examples
╔══════════╦═════════════════╦═══════════════╦═══════════════╗
║ 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() }
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)
}
╔══════════╦═════════════════╦═══════════════╦═══════════════╗
║ 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) }
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()}"
}
// 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)
}
*
* * *
* * * * *
* * * * * * *
// 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.
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.
╔══════════╦═════════════════╦═══════════════╦═══════════════╗
║ 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
}
//- 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) {
// ...
}
})
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
╔══════════╦═════════════════╦═══════════════╦═══════════════╗
║ 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
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("...") ║
╚══════════╩═════════════════╩═══════════════╩═══════════════╝
╔══════════╦═════════════════╦═══════════════╦═══════════════╗
║ 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