└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # kotlin-snippets 2 | 3 | ## Null Safety 4 | ```kotlin 5 | var b: String? = "abc" 6 | b = null // ok 7 | print(b) 8 | 9 | 10 | var b: String? = null 11 | if (b != null && b.isNotEmpty()) { 12 | print(b) 13 | } 14 | 15 | if (b?.isNotEmpty() == true){ 16 | print(b) 17 | } 18 | 19 | bob?.department?.head?.name 20 | 21 | val listWithNulls: List = listOf("Kotlin", null) 22 | for (item in listWithNulls) { 23 | item?.let { println(it) } // prints A and ignores null 24 | } 25 | 26 | 27 | //elvis 28 | val l = b?.length ?: -1 29 | 30 | //early return 31 | myVar ?: return 32 | 33 | //for NPE-lovers 34 | val l = b!!.length 35 | ``` 36 | 37 | ## String templates 38 | ```kotlin 39 | // simple name in template: 40 | var a = 1 41 | val s1 = "a is $a" 42 | a = 2 43 | // arbitrary expression in template: 44 | val s2 = "${s1.replace("is", "was")}, but now is $a" 45 | ```` 46 | 47 | ## Using if as an expression: 48 | ```kotlin 49 | fun maxOf(a: Int, b: Int) = if (a > b) a else b 50 | ``` 51 | 52 | ## Using type checks and automatic casts 53 | ```kotlin 54 | if (obj is String) { 55 | // `obj` is automatically cast to `String` in this branch 56 | return obj.length 57 | } 58 | ``` 59 | 60 | ## Using a for loop 61 | ```kotlin 62 | val items = listOf("apple", "banana", "kiwifruit") 63 | for (item in items) { 64 | println(item) 65 | } 66 | ``` 67 | 68 | ## Using when expression 69 | ```kotlin 70 | fun describe(obj: Any): String = 71 | when (obj) { 72 | 1 -> "One" 73 | "Hello" -> "Greeting" 74 | is Long -> "Long" 75 | !is String -> "Not a string" 76 | else -> "Unknown" 77 | } 78 | ``` 79 | 80 | 81 | ## Ranges 82 | ```kotlin 83 | for (x in 1..5) { 84 | print(x) 85 | } 86 | ``` 87 | 88 | ## Collections 89 | ```kotlin 90 | when { 91 | "orange" in items -> println("juicy") 92 | "apple" in items -> println("apple is fine too") 93 | } 94 | ``` 95 | 96 | Prefer using `when` if there are three or more options. 97 | 98 | ## Lambdas 99 | ```kotlin 100 | val fruits = listOf("banana", "avocado", "apple", "kiwifruit") 101 | fruits 102 | .filter { it.startsWith("a") } 103 | .sortedBy { it } 104 | .map { it.toUpperCase() } 105 | .forEach { println(it) } 106 | 107 | val numbers = listOf(1, 6, 2, 7, 8, 9) 108 | numbers.find { it > 3 } // 6 109 | numbers.findLast{it > 3} // 9 110 | numbers.any{it > 8} // true 111 | ``` 112 | 113 | 114 | ## Default function values 115 | ```kotlin 116 | fun foo(a: Int = 0, b: String = "") { ... } 117 | ``` 118 | 119 | ## Lists 120 | ```kotlin 121 | val list = listOf("a", "b", "c") 122 | val mutableList = arrayListOf("a", "b", "c") 123 | ``` 124 | 125 | ## Read-only map 126 | ```kotlin 127 | val map = mapOf("a" to 1, "b" to 2, "c" to 3) 128 | ``` 129 | 130 | ## Lazy property 131 | ```kotlin 132 | val preference: String by lazy { 133 | sharedPreferences.getString(PREFERENCE_KEY) 134 | } 135 | ``` 136 | 137 | ## Constructors 138 | ```kotlin 139 | class Person(firstName: String) { ... } 140 | 141 | class Customer public @Inject constructor(name: String) { ... } 142 | 143 | // secondary constructor 144 | class Person { 145 | constructor(parent: Person) { 146 | parent.children.add(this) 147 | } 148 | } 149 | 150 | class Constructors { 151 | init { 152 | println("Init block") // first 153 | } 154 | 155 | constructor(i: Int) { 156 | println("Constructor") // second 157 | } 158 | } 159 | ``` 160 | 161 | ## Getter setter 162 | ```kotlin 163 | var email:String = "" 164 | set(value) { 165 | if (value.isValidEmail()){ 166 | field = value 167 | } 168 | } 169 | ``` 170 | 171 | ## Late initialization 172 | ```kotlin 173 | class MyActivity : AppCompatActivity() { 174 | // non-null, but not initalized 175 | lateinit var recyclerView: RecyclerView 176 | 177 | override fun onCreate(savedInstanceState: Bundle?) { 178 | // … 179 | // initialized here 180 | recyclerView = findViewById(R.id.recycler_view) 181 | } 182 | } 183 | ``` 184 | 185 | ## Inline functions 186 | Calls will be replaced with the function body. 187 | ```kotlin 188 | // define an inline function that takes a function argument 189 | inline fun onlyIf(check: Boolean, operation: () -> Unit) { 190 | if (check) { 191 | operation() 192 | } 193 | } 194 | // call it like this 195 | onlyIf(shouldPrint) { // call: pass operation as a lambda 196 | println(“Hello, Kotlin”) 197 | } 198 | // which will be inlined to this 199 | if (shouldPrint) { // execution: no need to create lambda 200 | println(“Hello, Kotlin”) 201 | } 202 | ``` 203 | 204 | ## Extensions 205 | ```kotlin 206 | fun View.hide() { 207 | visibility = View.GONE 208 | } 209 | 210 | textView.hide() 211 | 212 | fun MutableList.swap(index1: Int, index2: Int) { 213 | val tmp = this[index1] // 'this' corresponds to the list 214 | this[index1] = this[index2] 215 | this[index2] = tmp 216 | } 217 | 218 | val l = mutableListOf(1, 2, 3) 219 | l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l' 220 | 221 | fun Int?.orZero() = this ?: 0 222 | val myInt:Int? = null 223 | println("${myInt.orZero()}") // will print 0 224 | ``` 225 | 226 | ## Data classes 227 | ```kotlin 228 | data class User(val name: String = "", val age: Int = 0) 229 | 230 | data class Person(val name: String) { 231 | var age: Int = 0 // excluded 232 | } 233 | 234 | //destructing 235 | val jane = User("Jane", 35) 236 | val (name, age) = jane 237 | println("$name, $age years of age") // prints "Jane, 35 years of age" 238 | ``` 239 | 240 | ## Sealed classes 241 | ```kotlin 242 | sealed class NetworkResult 243 | data class Success(val result: String): NetworkResult() 244 | data class Failure(val error: Error): NetworkResult() 245 | // one observer for success and failure 246 | viewModel.data.observe(this, Observer { data -> 247 | data ?: return@Observer // skip nulls 248 | when(data) { 249 | is Success -> showResult(data.result) // smart cast to Success 250 | is Failure -> showError(data.error) // smart cast to Failure 251 | } 252 | }) 253 | 254 | 255 | // use Sealed classes as ViewHolders in a RecyclerViewAdapter 256 | override fun onBindViewHolder( 257 | holder: SealedAdapterViewHolder?, position: Int) { 258 | when (holder) { // compiler enforces handling all types 259 | is HeaderHolder -> { 260 | holder.displayHeader(items[position]) // smart cast here 261 | } 262 | is DetailsHolder -> { 263 | holder.displayDetails(items[position]) // smart cast here 264 | } 265 | } 266 | } 267 | ``` 268 | 269 | ## Generics 270 | ```kotlin 271 | class ApiResponse(t:T){ 272 | var value = t 273 | } 274 | 275 | val r = ApiResponse("Data") 276 | val r = ApiResponse(User("name",20)) 277 | 278 | ``` 279 | 280 | ## Functions 281 | ```kotlin 282 | fun reformat(str: String, 283 | normalizeCase: Boolean = true, 284 | upperCaseFirstLetter: Boolean = true, 285 | divideByCamelHumps: Boolean = false, 286 | wordSeparator: Char = ' ') { 287 | ... 288 | } 289 | 290 | reformat(str) 291 | 292 | reformat(str, true, true, false, '_') 293 | 294 | reformat(str, 295 | normalizeCase = true, 296 | upperCaseFirstLetter = true, 297 | divideByCamelHumps = false, 298 | wordSeparator = '_' 299 | ) 300 | ``` 301 | 302 | ```kotlin 303 | fun asList(vararg ts: T): List { 304 | val result = ArrayList() 305 | for (t in ts) // ts is an Array 306 | result.add(t) 307 | return result 308 | } 309 | 310 | val list = asList(1, 2, 3) 311 | ``` 312 | 313 | ## infix notation 314 | ```kotlin 315 | infix fun Int.shl(x: Int): Int { ... } 316 | 317 | // calling the function using the infix notation 318 | 1 shl 2 319 | 320 | // is the same as 321 | 1.shl(2) 322 | ``` 323 | 324 | ## Local functions 325 | ```kotlin 326 | fun dfs(graph: Graph) { 327 | fun dfs(current: Vertex, visited: Set) { 328 | if (!visited.add(current)) return 329 | for (v in current.neighbors) 330 | dfs(v, visited) 331 | } 332 | 333 | dfs(graph.vertices[0], HashSet()) 334 | } 335 | ``` 336 | 337 | ## apply, also, run, let 338 | ```kotlin 339 | val string = "a" 340 | val result = string.apply { 341 | // this = "a" 342 | // it = not available 343 | 4 // Block return value unused 344 | // result = "a" 345 | } 346 | 347 | val string = "a" 348 | val result = string.also { 349 | // this = this@MyClass 350 | // it = "a" 351 | 4 // Block return value unused 352 | // result = "a" 353 | } 354 | 355 | 356 | val string = "a" 357 | val result = string.run { 358 | // this = "a" 359 | // it = not available 360 | 1 // Block return value 361 | // result = 1 362 | } 363 | 364 | val string = "a" 365 | val result = string.let { 366 | // this = this@MyClass 367 | // it = "a" 368 | 2 // Block return value 369 | // result = 2 370 | } 371 | ``` 372 | 373 | ## Higher order functions 374 | ```kotlin 375 | fun ArrayList.filterOnCondition(condition: (T) -> Boolean): ArrayList{ 376 | val result = arrayListOf() 377 | for (item in this){ 378 | if (condition(item)){ 379 | result.add(item) 380 | } 381 | } 382 | 383 | return result 384 | } 385 | ... 386 | fun isMultipleOf (number: Int, multipleOf : Int): Boolean{ 387 | return number % multipleOf == 0 388 | } 389 | ... 390 | var list = arrayListOf() 391 | for (number in 1..10){ 392 | list.add(number) 393 | } 394 | var resultList = list.filterOnCondition { isMultipleOf(it, 5) } 395 | ``` 396 | 397 | ## Coroutines 398 | 399 | ```kotlin 400 | GlobalScope.launch { 401 | delay(2000) // Suspend function waits until completed 402 | log("one") 403 | delay(1000) 404 | log("two") 405 | } 406 | log("three") 407 | 408 | /* 409 | Output will be 410 | 0ms > three 411 | 2000ms > one 412 | 3000ms > two 413 | */ 414 | ``` 415 | 416 | ```kotlin 417 | runBlocking{ 418 | val job = GlobalScope.launch { 419 | delay(2000) 420 | log("one") 421 | } 422 | log("two") 423 | job.join() // Job starts here waits until completed 424 | log("three") 425 | } 426 | 427 | /* 428 | Output will be 429 | 0ms > two 430 | 2000ms > one 431 | 2000ms > three 432 | */ 433 | ``` 434 | 435 | ```kotlin 436 | runBlocking { 437 | log("zero") 438 | launch { // launch block starts but doesn't wait to be completed. Because launch block is not suspend function 439 | delay(200) 440 | log("one") 441 | } 442 | 443 | launch { 444 | delay(300) 445 | log("two") 446 | } 447 | coroutineScope { // this block starts but waits for its all inner blocks until complete to process to the next line 448 | launch { 449 | delay(1000) 450 | log("three") 451 | } 452 | 453 | delay(500) 454 | log("four") 455 | } 456 | delay(1000) 457 | log("five") 458 | } 459 | /* 460 | Output will be 461 | 15.00.000 > zero 462 | 15.00.200 > one 463 | 15.00.300 > two 464 | 15.00.500 > four 465 | 15.01.000 > three 466 | 15.02.000 > five 467 | */ 468 | ``` 469 | Simple Presenter-Interactor Retrofit service call 470 | ```kotlin 471 | class MyPresenter(private val view: BaseView) { 472 | 473 | private val job = Job() 474 | private val dispatcher = Dispatchers.Main 475 | private val uiScope = CoroutineScope(dispatcher + job) 476 | val interactor = MyInteractor() 477 | 478 | fun getResult() { 479 | view.showLoading() 480 | 481 | uiScope.launch { 482 | val response = interactor.getResult() 483 | response.response?.apply { 484 | view.onGetResult(result) 485 | } 486 | response.error?.apply { view.onError(this) } 487 | view.hideLoading() 488 | } 489 | } 490 | 491 | fun onDestroy(){ 492 | job.cancel() 493 | } 494 | } 495 | 496 | class MyInteractor(private val service: DummyService) { 497 | 498 | suspend fun getResult(): ApiResponse { 499 | return service.getResult().defer() 500 | } 501 | 502 | } 503 | ``` 504 | 505 | Convert a Callback to suspend fun 506 | ```kotlin 507 | fun callWithListener(listener: OnSomethingListener) { 508 | if (condition) { 509 | listener.onSuccess("success") 510 | } else { 511 | listener.onFail(Exception("fail")) 512 | } 513 | } 514 | 515 | suspend fun convert2suspend(): String { 516 | return suspendCoroutine { continuation -> 517 | callWithListener(object : OnSomethingListener { 518 | override fun onSuccess(data: String) { 519 | continuation.resume(data) 520 | } 521 | 522 | override fun onFail(e: Exception) { 523 | continuation.resumeWithException(e) 524 | } 525 | }) 526 | } 527 | } 528 | 529 | runBlocking { 530 | try { 531 | val response = convert2suspend() 532 | println("response: $response") 533 | } catch (e: Exception) { 534 | println("failed: $e") 535 | } 536 | } 537 | ``` 538 | 539 | ## What's next? 540 | [Kotlin koans](https://kotlinlang.org/docs/tutorials/koans.html) 541 | 542 | [Coroutines](https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html) 543 | 544 | [Androidx](https://developer.android.com/topic/libraries/support-library/androidx-overview) 545 | 546 | [Try Kotlin Online](https://try.kotlinlang.org) 547 | 548 | 549 | 550 | ## Resources 551 | https://kotlinlang.org/ 552 | 553 | https://medium.com/google-developers/31daysofkotlin-week-1-recap-fbd5a622ef86 554 | 555 | https://medium.com/google-developers/31daysofkotlin-week-2-recap-9eedcd18ef8 556 | 557 | https://medium.com/google-developers/31daysofkotlin-week-3-recap-20b20ca9e205 558 | 559 | https://medium.com/google-developers/31daysofkotlin-week-4-recap-d820089f8090 560 | 561 | https://medium.com/@agrawalsuneet/higher-order-functions-in-kotlin-3d633a86f3d7 562 | --------------------------------------------------------------------------------