├── Kotlin In Action.md ├── readme.md └── android └── kotlin-android.mdown /Kotlin In Action.md: -------------------------------------------------------------------------------- 1 | # 2 Kotlin Basics 2 | ## 2.1 Basic Elements 3 | ### 2.1.1 Hello World 4 | ```kotlin 5 | fun main(args: Array){ 6 | println("Hello World") 7 | } 8 | ``` 9 | 10 | * Functions can be declared with no class. 11 | * Arrays are just classes 12 | * No Semicolon 13 | 14 | ### 2.1.2 Functions 15 | 16 | ```kotlin 17 | fun max(a: Int, b: Int): Int { 18 | return if (a > b) a else b 19 | } 20 | ``` 21 | * Return type after ) separated by : 22 | * `if` is an expression not a statement 23 | 24 | #### Statements vs Expressions 25 | * Expressions: has a value, which can be used as part of another expression. In Kotlin, most control structures, except for the loops (for, do, and do/ 26 | while) are expressions. 27 | * Statement is always a top-level element in its enclosing block and doesn’t have its own value. Assignments are expressions in Java and become statements in 28 | Kotlin 29 | 30 | #### Expression Bodies 31 | If the function body is a single expression you can use it as the entire body of the function 32 | 33 | ```kotlin 34 | fun max(a: Int, b: Int): Int = if (a > b) a else b 35 | ``` 36 | 37 | * Return body: if the function has braces 38 | * Expression body: if returns an expression 39 | 40 | #### Type inference 41 | Omitting the return type, and let the compiler _infer_ the type. (Only in Expression Bodies) 42 | 43 | ```kotlin 44 | fun max(a: Int, b: Int) = if (a > b) a else b 45 | ``` 46 | 47 | ### 2.1.3 Variables 48 | If a variable has an initializer 49 | 50 | ```kotlin 51 | val question = "The Ultimate Question of Life, the Universe, and Everything" 52 | val answer = 42 53 | ``` 54 | You can add the type, but is not needed 55 | ```kotlin 56 | val answer : Int = 42 57 | ``` 58 | If there is no initializer, you need to add the type 59 | 60 | ```kotlin 61 | val answer : Int 62 | answer = 42 63 | ``` 64 | 65 | #### Mutable and Immutable variables 66 | * `val`: (value) Immutable. (final in Java) 67 | * `var`: (variable) Mutable 68 | 69 | Use `val` unless necessary 70 | 71 | Initialize it with different values depending on some condition, if the compiler can ensure that only one of the initialization statements will 72 | be executed. 73 | 74 | ```kotlin 75 | val message: String 76 | if (canPerformOperation()) 77 | message = "Success" 78 | else 79 | message = "Error" 80 | ``` 81 | 82 | Objects that a `val` point can be changed 83 | 84 | ```kotlin 85 | val languages = arrayListOf("Java") // languages is an immutable reference 86 | languages.add("Kotlin") 87 | ``` 88 | 89 | `var` can change the value but not the type 90 | 91 | ```kotlin 92 | var answer = 42 93 | answer = "no answer" // Compile Error 94 | ``` 95 | 96 | ### 2.1.4 Easier string formatting: string templates 97 | ```kotlin 98 | var name = "Peter" 99 | println("Hello, $name!") 100 | println("Hello, ${args[0]}!") 101 | ``` 102 | 103 | ## 2.2 Classes and properties 104 | ``` 105 | class Person(val name: String) 106 | ``` 107 | 108 | * This kind of classes are called _value objects_ 109 | * `public` is not needed because all classes are public by default 110 | 111 | ### 2.2.1 Properties 112 | * _properties_ in Java are the combination of _fields_ and _accessors_ 113 | * In Kotlin _properties_ are declared with _var_ and _val_ depending on its mutability 114 | 115 | ``` 116 | class Person( 117 | val name: String, 118 | var isMarried: Boolean 119 | ) 120 | ``` 121 | * _accessors_ are built in automatically 122 | * Custom _accessors_ can be built 123 | * _properties_ are referenced directly `person.name` and if mutable `person.name = "John"` 124 | 125 | ### 2.2.2 Custom _accessors_ 126 | ```php 127 | class Rectangle(val height: Int, val width: Int) { 128 | val isSquare: Boolean 129 | get() = return height == width 130 | } 131 | ``` 132 | * The property isSquare doesn’t need a field to store its value. 133 | * It only has a custom getter with the implementation provided. 134 | * The value is computed every time the property is accessed. 135 | 136 | ## 2.3 Representing and handling choices: enums and “when” 137 | ### 2.3.1 Declaring enum classes 138 | ```php 139 | enum class Color { 140 | RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET 141 | } 142 | ``` 143 | * `enum` is a soft keyword (i.e. you can use enum in your variable names) 144 | * `class` is a keyword 145 | 146 | You can declare properties and methods on enum classes 147 | 148 | ```php 149 | enum class Color(val r: Int, val g: Int, val b: Int) { 150 | RED(255, 0, 0), 151 | ORANGE(255, 165, 0), 152 | YELLOW(255, 255, 0), 153 | GREEN(0, 255, 0), 154 | BLUE(0, 0, 255), 155 | INDIGO(75, 0, 130), 156 | VIOLET(238, 130, 238); 157 | fun rgb() = (r * 256 + g) * 256 + b 158 | } 159 | ``` 160 | * _enum constants_: `val r : Int` 161 | * _enum constants_ need to be declared for each constant 162 | * semicolon is needed to separate _enum constants_ list from method definitions 163 | 164 | ### 2.3.2 Using “when” to deal with enum classes 165 | ```kotlin 166 | fun getWarmth(color: Color) = 167 | when(color) { 168 | Color.RED, Color.ORANGE, Color.YELLOW -> "warm" 169 | Color.GREEN -> "neutral" 170 | Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold" 171 | } 172 | ``` 173 | 174 | Import enum constants to access without qualifier 175 | 176 | ```kotlin 177 | import ch02.colors.Color //Imports the Color class declared 178 | import ch02.colors.Color.* //Explicitly imports enum constants to us them by names 179 | fun getWarmth(color: Color) = when(color) { 180 | RED, ORANGE, YELLOW -> "warm" 181 | GREEN -> "neutral" 182 | BLUE, INDIGO, VIOLET -> "cold" 183 | } 184 | ``` 185 | 186 | ### 2.3.3 Using “when” with arbitrary objects 187 | Being able to use any expression as a when branch condition lets you write concise and beautiful code in many cases. 188 | 189 | ```kotlin 190 | fun mix(c1: Color, c2: Color) = 191 | when (setOf(c1, c2)) { 192 | setOf(RED, YELLOW) -> ORANGE 193 | setOf(YELLOW, BLUE) -> GREEN 194 | setOf(BLUE, VIOLET) -> INDIGO 195 | else -> throw Exception("Dirty color") 196 | } 197 | ``` 198 | ### 2.3.4 Using `when` without an argument 199 | If no argument is supplied for the when expression, the branch condition is any Boolean expression. 200 | 201 | ```kotlin 202 | fun mixOptimized(c1: Color, c2: Color) = 203 | when { 204 | (c1 == RED && c2 == YELLOW) || 205 | (c1 == YELLOW && c2 == RED) -> 206 | ORANGE 207 | (c1 == YELLOW && c2 == BLUE) || 208 | (c1 == BLUE && c2 == YELLOW) -> 209 | GREEN 210 | (c1 == BLUE && c2 == VIOLET) || 211 | (c1 == VIOLET && c2 == BLUE) -> 212 | INDIGO 213 | else -> throw Exception("Dirty color") 214 | } 215 | ``` 216 | 217 | ### 2.3.5 Smart casts: combining type checks and casts 218 | * `is` similar to Java's `instanceOf` 219 | * *smart cast*: `is` do the cast automatically so you don't need to do it manually as in Java. 220 | 221 | ```kotlin 222 | if (e is Sum) { 223 | return eval(e.right) + eval(e.left) 224 | } 225 | ``` 226 | 227 | ### 3.3.6 Refactoring: replacing “if” with “when” 228 | `when` branches check the argument type and apply smart casts 229 | 230 | ```kotlin 231 | fun eval(e: Expr): Int = 232 | when (e) { 233 | is Num -> e.value 234 | is Sum -> eval(e.right) + eval(e.left) 235 | else -> throw IllegalArgumentException("Unknown expression") 236 | } 237 | ``` 238 | 239 | ### 2.3.7 Blocks as branches of “if” and “when” 240 | * `if` and `when` can have blocks as branches. 241 | * The last expression in the block is the result. 242 | 243 | ```kotlin 244 | ... 245 | is Num -> { 246 | println("num: ${e.value}") 247 | e.value // returned value 248 | }... 249 | } 250 | ``` 251 | 252 | This does not apply for regular functions. They must have a `return` 253 | 254 | ## 2.4 Iterating over things: “while” and “for” loops 255 | ### 2.4.1 The “while” loop 256 | The body is executed while the condition is true. 257 | 258 | ```kotlin 259 | while (condition) { 260 | ... 261 | } 262 | ``` 263 | 264 | The body is executed for the first time unconditionally. After that, it’s executed while the condition is true. 265 | 266 | ```kotlin 267 | do { 268 | ... 269 | } while (condition) 270 | ``` 271 | 272 | ### 2.4.2 Iterating over numbers: ranges and progressions 273 | * _range_: essentially an interval between two values. `..` Last value is always in the range. (closed/inclusive range) 274 | 275 | ```kotlin 276 | val oneToTen = 1..10 277 | ``` 278 | 279 | * You can change the step and the order 280 | 281 | ```kotlin 282 | for (i in 100 downTo 1 step 2) 283 | ``` 284 | 285 | * Iteration in a range 286 | 287 | ```kotlin 288 | for (x in 0 until size) // for (x in 0..size-1) 289 | ``` 290 | 291 | ### 2.4.3 Iterating over maps 292 | The .. syntax to create a range works not only for numbers, but also for characters 293 | 294 | ```kotlin 295 | val binaryReps = TreeMap() // in TreeMaps, keys are sorted 296 | for (c in 'A'..'F') { 297 | val binary = Integer.toBinaryString(c.toInt()) 298 | binaryReps[c] = binary 299 | } 300 | for ((letter, binary) in binaryReps) { // Iterates over a map, assigning the map key and value to two variables 301 | println("$letter = $binary") 302 | } 303 | ``` 304 | 305 | You can get the index of a collection 306 | 307 | ```kotlin 308 | val list = arrayListOf("10", "11", "1001") 309 | for ((index, element) in list.withIndex()) { 310 | println("$index: $element") 311 | } 312 | ``` 313 | 314 | ### 2.4.4 Using “in” to check collection and range membership 315 | You can use the `in` operator to check whether a value is in a range, or its opposite, `!in` 316 | 317 | ```kotlin 318 | fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' 319 | fun isNotDigit(c: Char) = c !in '0'..'9' 320 | ``` 321 | Works also on _when expressions_ 322 | 323 | ```kotlin 324 | fun recognize(c: Char) = when (c) { 325 | in '0'..'9' -> "It's a digit!" 326 | in 'a'..'z', in 'A'..'Z' -> "It's a letter!" 327 | else -> "I don't know…" 328 | } 329 | ``` 330 | You can use the `in` in any class that implements the `java.lang.Comparable` interface 331 | 332 | ## 2.5 Exceptions in Kotlin 333 | * You don’t have to use the new keyword to create an instance of the exception 334 | * `throw` construct is an expression 335 | 336 | ```kotlin 337 | val percentage = 338 | if (number in 0..100) 339 | number 340 | else 341 | throw IllegalArgumentException("A percentage value must be between 0 and 100: $number") 342 | 343 | ``` 344 | ### 2.5.1 “try”, “catch”, and “finally” 345 | * Kotlin doesn’t differentiate between checked and unchecked exceptions 346 | * You don’t have to explicitly specify exceptions that can be thrown 347 | 348 | ### 2.5.2 “try” as an expression 349 | * If the execution of a try code block behaves normally, the last expression in the block is the result. 350 | * If an exception is caught, the last expression in a corresponding catch block is the result. 351 | 352 | ```kotlin 353 | fun readNumber(reader: BufferedReader) { 354 | val number = try { 355 | Integer.parseInt(reader.readLine()) 356 | } catch (e: NumberFormatException) { 357 | null 358 | } 359 | println(number) 360 | } 361 | ``` 362 | ## 2.6 Summary 363 | * The `fun` keyword is used to declare a function. The `val` and `var` keywords declare read-only and mutable variables, respectively. 364 | * String templates help you avoid noisy string concatenation. Prefix a variable name with `$` or surround an expression with `${ }` to have its value injected into the string. 365 | * Value-object classes are expressed in a concise way in Kotlin. 366 | * The familiar `if` is now an expression with a return value. 367 | * The `when` expression is analogous to switch in Java but is more powerful. 368 | * You don’t have to cast a variable explicitly after checking that it has a certain type: the compiler casts it for you automatically using a **smart cast**. 369 | * The `for`, `while`, and `do-while` loops are similar to their counterparts in Java, but the `for loop` is now more convenient, especially when you need to iterate over a map or a collection with an index. 370 | * The concise syntax `1..5` creates a range. Ranges and progressions allow Kotlin to use a uniform syntax and set of abstractions in for loops and also work with the `in` and `!in` operators that check whether a value belongs to a range. 371 | * Exception handling in Kotlin is very similar to that in Java, except that **Kotlin doesn’t require you to declare the exceptions that can be thrown** by a function. 372 | 373 | # 3 Defining and calling functions 374 | ## 3.1 Creating collections in Kotlin 375 | Kotlin uses the standard Java collection classes 376 | 377 | ```kotlin 378 | val set = hashSetOf(1, 7, 53) // java.util.HashSet 379 | val list = arrayListOf(1, 7, 53) // java.util.ArrayList 380 | val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three") // java.util.HashMap 381 | ``` 382 | You can do more with Kotlin than with Java. 383 | ie 384 | 385 | ```kotlin 386 | val strings = listOf("first", "second", "fourteenth") 387 | println(strings.last())// fourteenth 388 | 389 | val numbers = setOf(1, 14, 2) 390 | println(numbers.max()) // 14 391 | ``` 392 | 393 | ## 3.2 Making functions easier to call 394 | Sample call to joinToString 395 | 396 | ```kotlin 397 | fun joinToString( 398 | collection: Collection, 399 | separator: String, 400 | prefix: String, 401 | postfix: String 402 | ): String { 403 | 404 | val result = StringBuilder(prefix) 405 | for ((index, element) in collection.withIndex()) { 406 | if (index > 0) result.append(separator) 407 | result.append(element) 408 | } 409 | result.append(postfix) 410 | return result.toString() 411 | } 412 | ``` 413 | Call 414 | 415 | ```kotlin 416 | val list = listOf(1, 2, 3) 417 | println(list) // [1, 2, 3] 418 | 419 | val list = listOf(1, 2, 3) 420 | println(joinToString(list, "; ", "(", ")")) // (1; 2; 3) 421 | ``` 422 | 423 | ### 3.2.1 Named Arguments 424 | To make easier the call `joinToString(collection, " ", " ", ".")` in Kotlin we can add the name of the parameter to the call. 425 | 426 | ```kotlin 427 | joinToString(collection, separator = " ", prefix = " ", postfix = ".") 428 | ``` 429 | * If you specify the name of an argument in a call, you should also specify the names for all the arguments after that 430 | * You can’t use named arguments when calling methods written in Java 431 | 432 | ### 3.2.2 Default parameters values 433 | To reduce the number of overloaded methods, we can use default parameters in Kotlin. 434 | 435 | ```kotlin 436 | fun joinToString( 437 | collection: Collection, 438 | separator: String = ", ", 439 | prefix: String = "", 440 | postfix: String = "" 441 | ): String 442 | ``` 443 | ```kotlin 444 | joinToString(list, ", ", "", "") // 1, 2, 3 445 | joinToString(list) // 1, 2, 3 446 | joinToString(list, "; ") // 1; 2; 3 447 | ``` 448 | 449 | * When using the regular call syntax, you can omit only trailing arguments. 450 | * When using named arguments, you can omit some arguments from the middle of the list 451 | 452 | ```kotlin 453 | joinToString(list, suffix = ";", prefix = "# ") // 1, 2, 3 454 | ``` 455 | 456 | * _To use default values from JAVA use `@JVMOverloads`_ 457 | 458 | ### 3.2.3 Getting rid of static utility classes: **top-level** functions and properties 459 | * In Kotlin you can place functions at the *top level* of a source file, outside of any class. 460 | * They are still members of the package declared at the top of the file. 461 | * You still need to import them if you want to call them from other packages. 462 | 463 | ```kotlin 464 | package strings 465 | fun joinToString(...): String { ... } 466 | ``` 467 | * _To use top-level functions from JAVA call it as `NameOfTheFileContainingTheFunction.functionName()`_ 468 | 469 | #### top-level properties 470 | ```kotlin 471 | var opCount = 0 472 | fun performOperation() {opCount++} 473 | fun reportOperationCount() {println("Operation performed $opCount times")} 474 | ``` 475 | They are stored in static files. 476 | 477 | * `const` makes them immutable. In JAVA: `static final` 478 | 479 | ## 3.3 Adding methods to other people’s classes: **extension** functions and properties 480 | Extension function: a function that can be called as a member of a class but is defined outside of it. 481 | 482 | * `String` is the _receiver type_ 483 | * `this` is the _receiver object_ ("Kotlin" in the example) 484 | * `this` can be omitted 485 | 486 | ```kotlin 487 | fun String.lastChar(): Char = this.get(this.length - 1) 488 | 489 | println("Kotlin".lastChar()) // n 490 | ``` 491 | * You can not break encapsulation. From a extension function you don't have access to private or protected members. 492 | 493 | ### 3.3.1 Imports and extension functions 494 | * You have to import them 495 | * You can import individual functions 496 | 497 | ```kotlin 498 | import strings.lastChar or //strings.* 499 | "Kotlin".lastChar() 500 | ``` 501 | You can change the name of the function 502 | 503 | ```kotlin 504 | import strings.lastChar as last 505 | "Kotlin".last() 506 | ``` 507 | 508 | ### 3.3.2 Calling extension function from Java 509 | * An extension function is a static method that accepts the receiver object as its first argument. 510 | * As with other top-level functions, the name of the Java class containing the method is determined from the name of the file where the function is declared. 511 | * Is declared as a top-level function, so it’s compiled to a static method. 512 | 513 | ```kotlin 514 | /* Java */ 515 | char c = StringUtilKt.lastChar("Java"); 516 | ``` 517 | 518 | ### 3.3.3 Utility functions as extensions 519 | ```kotlin 520 | fun Collection.joinToString( 521 | separator: String = ", ", 522 | prefix: String = "", 523 | postfix: String = "" 524 | ): String { 525 | val result = StringBuilder(prefix) 526 | for ((index, element) in this.withIndex()) 527 | if (index > 0) result.append(separator) 528 | result.append(element) 529 | } 530 | result.append(postfix) 531 | return result.toString() 532 | } 533 | 534 | val list = arrayListOf(1, 2, 3) 535 | list.joinToString(" ")// 1 2 3 536 | ``` 537 | 538 | If we want to use it only in Strings. 539 | 540 | ```kotlin 541 | fun Collection.join( 542 | separator: String = ", ", 543 | prefix: String = "", 544 | postfix: String = "" 545 | ) = joinToString(separator, prefix, postfix) 546 | 547 | listOf("one", "two", "eight").join(" ")// one two eight 548 | ``` 549 | 550 | ### 3.3.4 No overriding for extension functions 551 | * You can’t override an extension function 552 | * The function that’s called depends on the declared static type of the variable, not on the runtime type of the value stored in that variable. 553 | 554 | ```kotlin 555 | fun View.showOff() = println("I'm a view!") 556 | fun Button.showOff() = println("I'm a button!") 557 | 558 | val view: View = Button() 559 | view.showOff() // I'm a view! 560 | ``` 561 | 562 | * If the class has a member function with the same signature as an extension function, the member function always takes precedence. 563 | 564 | ### Extension properties 565 | * Extend classes with APIs that can be accessed using the property syntax, be accessed using the property syntax, 566 | * They’re called properties, they can’t have any state. (there’s no proper place to store it) 567 | 568 | ```kotlin 569 | val String.lastChar: Char 570 | get() = get(length - 1) 571 | ``` 572 | * The getter must always be defined. There’s no backing field and therefore no default getter implementation. 573 | * Initializers aren’t allowed for the same reason. 574 | 575 | Adding a setter they can be mutable 576 | 577 | ```kotlin 578 | var StringBuilder.lastChar: Char 579 | get() = get(length - 1) 580 | set(value: Char) { 581 | this.setCharAt(length - 1, value) 582 | } 583 | println("Kotlin".lastChar) // n 584 | val sb = StringBuilder("Kotlin?") 585 | sb.lastChar = '!' 586 | println(sb)// Kotlin! 587 | ``` 588 | 589 | * From Java, you should invoke its getter explicitly: `StringUtilKt.getLastChar("Java")` 590 | 591 | ## 3.4 Working with collections: varargs, infix calls, and library support 592 | ### 3.4.2 Varargs: functions that accept an arbitrary number of arguments 593 | * Used in `listOf` for example 594 | 595 | ```kotlin 596 | val list = listOf(2, 3, 5, 7, 11) 597 | ``` 598 | * Uses `vararg` modifier on the parameter insted of the `...` from Java 599 | 600 | ```kotlin 601 | fun listOf(vararg values: T): List { ... } 602 | ``` 603 | 604 | * Arrays in Java are passed as is, but in Kotlin they have to be unpacked. You can use the _spread operator_: `*`. 605 | 606 | ```kotlin 607 | fun main(args: Array) { 608 | val list = listOf("args: ", *args) 609 | println(list) 610 | } 611 | ``` 612 | 613 | ### 3.4.3 Working with pairs: infix calls and destructuring declarations 614 | In an infix call, the method name is placed immediately between the target object name and the parameter 615 | 616 | ```kotlin 617 | 1.to("one") === 1 to "one" 618 | ``` 619 | 620 | * Use `infix` modifier. 621 | 622 | ```kotlin 623 | infix fun Any.to(other: Any) = Pair(this, other) 624 | ``` 625 | 626 | #### _Destructuring declaration_ 627 | ```kotlin 628 | val (number, name) = 1 to "one" 629 | ``` 630 | It is used in loops 631 | 632 | ```kotlin 633 | for ((index, element) in collection.withIndex()) { 634 | println("$index: $element") 635 | } 636 | ``` 637 | 638 | ## 3.5 Working with strings and regular expressions 639 | Kotlin Strings are the same as Java Strings 640 | ### 3.5.1 Splitting strings 641 | * To avoid the confusion in Java with the split, dots and regular expressions Kotlin have overloaded extension. One that take a string and another one that take a Regex. 642 | * You can also give more than one separator to the split method. `"12.345-6.A".split(".", "-")` 643 | 644 | ### 3.5.2 Regular expressions and triple-quoted strings 645 | Using string extension functions is easy to parse the path, file name and extension of a file. 646 | 647 | ```kotlin 648 | fun parsePath(path: String) { 649 | val directory = path.substringBeforeLast("/") 650 | val fullName = path.substringAfterLast("/") 651 | val fileName = fullName.substringBeforeLast(".") 652 | val extension = fullName.substringAfterLast(".") 653 | println("Dir: $directory, name: $fileName, ext: $extension") 654 | } 655 | parsePath("/Users/yole/kotlin-book/chapter.adoc") 656 | // Dir: /Users/yole/kotlin-book, name: chapter, ext: adoc 657 | ``` 658 | 659 | #### _Triple-quoted string_ and Regex 660 | In _triple-quoted string_, you don’t need to escape any characters, including the backslash, so you can encode the dot symbol with `\.` rather than `\\.` 661 | 662 | ```kotlin 663 | fun parsePath(path: String) { 664 | val regex = """(.+)/(.+)\.(.+)""".toRegex() 665 | val matchResult = regex.matchEntire(path) 666 | if (matchResult != null) { 667 | val (directory, filename, extension) = matchResult.destructured 668 | println("Dir: $directory, name: $filename, ext: $extension") 669 | } 670 | } 671 | ``` 672 | ### 3.5.3 Multiline triple-quoted strings 673 | * _triple-quoted strings_ allows embed in your programs text containing line breaks 674 | * You can trim the indentation using `trimMargin` to have a better representation. 675 | 676 | ```kotlin 677 | val kotlinLogo = """| // 678 | .|// 679 | .|/ \""" 680 | >>> println(kotlinLogo.trimMargin(".")) 681 | | // 682 | |// 683 | |/ \ 684 | ``` 685 | ### 3.6 Making your code tidy: _local functions_ and extensions 686 | * Making DRY principle less boilerplate and cleaned 687 | * Keep the structure of the class. (Functions are inside the methods where are called.) 688 | 689 | From 690 | 691 | ```kotlin 692 | class User(val id: Int, val name: String, val address: String) 693 | 694 | fun saveUser(user: User) { 695 | if (user.name.isEmpty()) {// <- Repeated 696 | throw IllegalArgumentException("Can't save user ${user.id}: empty Name") // <- Repeated 697 | } 698 | if (user.address.isEmpty()) { // <- Repeated 699 | throw IllegalArgumentException("Can't save user ${user.id}: empty Address") // <- Repeated 700 | } 701 | // Save user to the database 702 | } 703 | ``` 704 | 705 | * Local functions: You can nest the functions you’ve extracted in the containing function 706 | * Local functions have access to all parameters and variables of the enclosing function 707 | * Local functions keep separated functions that are only relevant to a parent function, keeping the class clean and clear. 708 | * Local functions can access its public members without extra qualification 709 | * Try not to have more than one level of nesting 710 | 711 | ```kotlin 712 | class User(val id: Int, val name: String, val address: String) 713 | 714 | fun saveUser(user: User) { 715 | fun validate(value: String, fieldName: String) { 716 | if (value.isEmpty()) { 717 | throw IllegalArgumentException( "Can't save user ${user.id}: empty $fieldName") 718 | } 719 | } 720 | validate(user.name, "Name") 721 | validate(user.address, "Address") 722 | // Save user to the database 723 | } 724 | ``` 725 | 726 | To a extension function 727 | 728 | ```kotlin 729 | class User(val id: Int, val name: String, val address: String) 730 | 731 | fun User.validateBeforeSave() { 732 | fun validate(value: String, fieldName: String) { 733 | if (value.isEmpty()) { 734 | throw IllegalArgumentException( "Cant save user ${user.id}: empty $fieldName") 735 | } 736 | } 737 | validate(name, "Name") 738 | validate(address, "Address") 739 | } 740 | 741 | fun saveUser(user: User) { 742 | user.validateBeforeSave() 743 | // Save user to the database 744 | } 745 | } 746 | ``` 747 | ## 3.7 Summary 748 | * Kotlin doesn’t define its own collection classes and instead enhances the Java collection classes with a richer API. 749 | * Defining default values for function parameters greatly reduces the need to define overloaded functions, and the named-argument syntax makes calls to functions with many parameters much more readable. 750 | * Functions and properties can be declared directly in a file, not just as members of a class, allowing for a more flexible code structure. 751 | * Extension functions and properties let you extend the API of any class, including classes defined in external libraries, without modifying its source code and with no runtime overhead. 752 | * Infix calls provide a clean syntax for calling operator-like methods with a single argument. 753 | * Kotlin provides a large number of convenient string-handling functions for both regular expressions and plain strings. 754 | * Triple-quoted strings provide a clean way to write expressions that would require a lot of noisy escaping and string concatenation in Java. 755 | * Local functions help you structure your code more cleanly and eliminate duplication 756 | 757 | # 4 Classes Objects & Interfaces 758 | ## 4.1 Defining class hierarchies 759 | ### 4.1.1 Interfaces in Kotlin 760 | Contain definitions of abstract methods as well as implementations of non-abstract methods 761 | ```kotlin 762 | interface Clickable { 763 | fun click() 764 | fun showOff() = println("I'm clickable!") 765 | fun clicked() = println("I was clicked!") 766 | } 767 | ``` 768 | If two interfaces have a default method with the same name, the class that implements them must override both methods. 769 | 770 | ```kotlin 771 | class Button : Clickable, Focusable { 772 | override fun click() = println("I was clicked") 773 | override fun showOff() { 774 | super.showOff() 775 | super.showOff() 776 | } 777 | } 778 | ``` 779 | “super” qualified by the supertype name in angle brackets specifies the parent whose method you want to call. 780 | 781 | ### 4.1.2 Open, final, and abstract modifiers: final by default 782 | The `fragile base` class problem occurs when modifications of a base class can cause incorrect behavior of subclasses because the changed code of the base class no longer matches the assumptions in its subclasses. 783 | 784 | To protect against this problem, classes and methods that aren’t specifically intended to be overridden in subclasses ought to be explicitly marked as `final`. _Effective Java by Joshua Bloch_ 785 | 786 | In Kotlin classes and methods are `final` by default. 787 | 788 | If you want to allow the creation of subclasses of a class, you need to mark the class with the `open` modifier. Also in every property or method that can be overridden. 789 | 790 | ```kotlin 791 | // This class is open: others can inherit from it. 792 | open class RichButton : Clickable { 793 | // This function is final: you can’t override it in a subclass. 794 | fun disable() {} 795 | // This function is open: you may override it in a subclass. 796 | open fun animate() {} 797 | // This function overrides an open function and is open as well. 798 | override fun click() {} 799 | } 800 | ``` 801 | 802 | if you override a member of a base class or interface, the overriding member will also be `open` by default. In case you don't want this, annotate it as `final` 803 | 804 | ```kotlin 805 | open class RichButton : Clickable { 806 | final override fun click() {} 807 | } 808 | ``` 809 | 810 | > One significant benefit of classes that are `final` by default is that they enable smart 811 | casts in a larger variety of scenarios. 812 | * Smart casts work only for variables that couldn’t have changed after the type check. 813 | * For a class, only a class property that is a `val` and that 814 | doesn’t have a custom accessor. 815 | * This means that the property has to be `final` 816 | * Properties are `final` by default, you can use smart without thinking about it explicitly 817 | 818 | Abstract members are always open, so you don’t need to use an explicit open modifier 819 | 820 | ```kotlin 821 | //This class is abstract: you can’t create an instance of it. 822 | abstract class Animated { 823 | // This function is abstract 824 | abstract fun animate() 825 | // Non-abstract functions in abstract classes aren’t open by default 826 | open fun stopAnimating() {} 827 | fun animateTwice() {} 828 | } 829 | ``` 830 | | Modifier | Corresponding member | Comments for classes| Interfaces | 831 | |-----------|----------------------|----------|---| 832 | |final| Can’t be overridden| Used by default for class members|n/a| 833 | |open| Can be overridden |Should be specified explicitly|n/a| 834 | |abstract |Must be overridden| Can be used only in abstract classes; abstract members can’t have an implementation|n/a| 835 | |override| Overrides a member in a superclass or interface|Overridden member is open by default, if not marked final|---| 836 | 837 | 838 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Hello Kotlin 2 | ## Hello World 3 | We declare a package-level function main which returns Unit and takes 4 | an Array of strings as a parameter. Note that semicolons are optional. 5 | 6 | ``` kotlin 7 | 8 | fun main(args: Array) { 9 | println("Hello, world!") 10 | } 11 | ``` 12 | 13 | 14 | ## Reading a name from the command line 15 | Line 13 demonstrates string templates and array access. 16 | See this pages for details: 17 | http://kotlinlang.org/docs/reference/basic-types.html#strings 18 | http://kotlinlang.org/docs/reference/basic-types.html#arrays 19 | 20 | 21 | ``` kotlin 22 | 23 | fun main(args: Array) { 24 | if (args.size == 0) { 25 | println("Please provide a name as a command-line argument") 26 | return 27 | } 28 | println("Hello, ${args[0]}!") 29 | } 30 | ``` 31 | 32 | ## Reading many names from the command line 33 | Line 2 demonstrates the for-loop, that would have been called "enhanced" 34 | if there were any other for-loop in Kotlin. 35 | See http://kotlinlang.org/docs/reference/basic-syntax.html#using-a-for-loop 36 | 37 | 38 | ``` kotlin 39 | 40 | fun main(args: Array) { 41 | for (name in args) 42 | println("Hello, $name!") 43 | 44 | for (i in args.indices) 45 | print(args[i]) 46 | } 47 | ``` 48 | 49 | ## _When_ 50 | In this example, `val` denotes a declaration of a read-only local variable, 51 | that is assigned an pattern matching expression. 52 | See http://kotlinlang.org/docs/reference/control-flow.html#when-expression 53 | 54 | 55 | ``` kotlin 56 | 57 | fun main(args: Array) { 58 | val language = if (args.size == 0) "EN" else args[0] 59 | println(when (language) { 60 | "EN" -> "Hello!" 61 | "FR" -> "Salut!" 62 | "IT" -> "Ciao!" 63 | else -> "Sorry, I can't greet you in $language yet" 64 | }) 65 | } 66 | ``` 67 | 68 | ## OOP Simple Class 69 | Here we have a class with a primary constructor and a member function. 70 | Note that there's no `new` keyword used to create an object. 71 | See http://kotlinlang.org/docs/reference/classes.html#classes 72 | 73 | 74 | ``` kotlin 75 | 76 | class Greeter(val name: String) { 77 | fun greet() { 78 | println("Hello, ${name}"); 79 | } 80 | } 81 | 82 | fun main(args: Array) { 83 | Greeter(args[0]).greet() 84 | } 85 | ``` 86 | # Basics 87 | ## _if_ 88 | `if` is an expression, i.e. it returns a value. 89 | Therefore there is no ternary operator (condition ? then : else), 90 | because ordinary `if` works fine in this role. 91 | See http://kotlinlang.org/docs/reference/control-flow.html#if-expression 92 | 93 | ``` kotlin 94 | 95 | fun main(args: Array) { 96 | println(max(Integer.parseInt(args[0]), Integer.parseInt(args[1]))) 97 | } 98 | 99 | fun max(a: Int, b: Int) = if (a > b) a else b 100 | 101 | ``` 102 | 103 | ## Nullables 104 | A reference must be explicitly marked as nullable to be able hold a null. 105 | See http://kotlinlang.org/docs/reference/null-safety.html#null-safety 106 | 107 | ``` kotlin 108 | 109 | package multiplier 110 | 111 | // Return null if str does not hold a number 112 | fun parseInt(str: String): Int? { 113 | try { 114 | return Integer.parseInt(str) 115 | } catch (e: NumberFormatException) { 116 | println("One of the arguments isn't Int") 117 | } 118 | return null 119 | } 120 | 121 | fun main(args: Array) { 122 | if (args.size < 2) { 123 | println("No number supplied"); 124 | } else { 125 | val x = parseInt(args[0]) 126 | val y = parseInt(args[1]) 127 | 128 | // We cannot say 'xy' now because they may hold nulls 129 | if (x != null && y != null) { 130 | print(xy) // Now we can 131 | } else { 132 | println("One of the arguments is null") 133 | } 134 | } 135 | } 136 | ``` 137 | ## _is_ 138 | The `is` operator checks if an expression is an instance of a type and more. 139 | If we is-checked an immutable local variable or property, there's no need 140 | to cast it explicitly to the is-checked type. 141 | See this pages for details: 142 | http://kotlinlang.org/docs/reference/classes.html#classes-and-inheritance 143 | http://kotlinlang.org/docs/reference/typecasts.html#smart-casts 144 | 145 | ``` kotlin 146 | 147 | fun main(args: Array) { 148 | println(getStringLength("aaa")) 149 | println(getStringLength(1)) 150 | } 151 | 152 | fun getStringLength(obj: Any): Int? { 153 | if (obj is String) 154 | return obj.length // no cast to String is needed 155 | return null 156 | } 157 | ``` 158 | ## _while_ and _do..while_ 159 | See http://kotlinlang.org/docs/reference/control-flow.html#while-loops 160 | 161 | ``` kotlin 162 | 163 | fun main(args: Array) { 164 | var i = 0 165 | while (i < args.size) 166 | println(args[i++]) 167 | } 168 | ``` 169 | 170 | ## Ranges 171 | Check if a number lies within a range. 172 | Check if a number is out of range. 173 | Check if a collection contains an object. 174 | See http://kotlinlang.org/docs/reference/ranges.html#ranges 175 | 176 | 177 | ``` kotlin 178 | 179 | fun main(args: Array) { 180 | val x = Integer.parseInt(args[0]) 181 | //Check if a number lies within a range: 182 | val y = 10 183 | if (x in 1..y - 1) 184 | println("OK") 185 | 186 | //Iterate over a range: 187 | for (a in 1..5) 188 | print("${a} ") 189 | 190 | //Check if a number is out of range: 191 | println() 192 | val array = arrayListOf() 193 | array.add("aaa") 194 | array.add("bbb") 195 | array.add("ccc") 196 | 197 | if (x !in 0..array.size - 1) 198 | println("Out: array has only ${array.size} elements. x = ${x}") 199 | 200 | //Check if a collection contains an object: 201 | if ("aaa" in array) // collection.contains(obj) is called 202 | println("Yes: array contains aaa") 203 | 204 | if ("ddd" in array) // collection.contains(obj) is called 205 | println("Yes: array contains ddd") 206 | else 207 | println("No: array doesn't contains ddd") 208 | } 209 | ``` 210 | 211 | ## Control flow _when_ 212 | See http://kotlinlang.org/docs/reference/control-flow.html#when-expression 213 | 214 | 215 | ``` kotlin 216 | 217 | fun main(args: Array) { 218 | cases("Hello") 219 | cases(1) 220 | cases(System.currentTimeMillis()) 221 | cases(MyClass()) 222 | cases("hello") 223 | } 224 | 225 | fun cases(obj: Any) { 226 | when (obj) { 227 | 1 -> println("One") 228 | "Hello" -> println("Greeting") 229 | is Long -> println("Long") 230 | !is String -> println("Not a string") 231 | else -> println("Unknown") 232 | } 233 | } 234 | 235 | class MyClass() { 236 | } 237 | ``` 238 | 239 | # Multi-declarations and Data classes 240 | ## Multi-declarations 241 | 242 | This example introduces a concept that we call mutli-declarations. 243 | It creates multiple variable at once. Anything can be on the right-hand 244 | side of a mutli-declaration, as long as the required number of component 245 | functions can be called on it. 246 | See http://kotlinlang.org/docs/reference/multi-declarations.html#multi-declarations 247 | 248 | ``` kotlin 249 | 250 | fun main(args: Array) { 251 | val pair = Pair(1, "one") 252 | 253 | val (num, name) = pair 254 | 255 | println("num = $num, name = $name") 256 | } 257 | 258 | class Pair(val first: K, val second: V) { 259 | operator fun component1(): K { 260 | return first 261 | } 262 | 263 | operator fun component2(): V { 264 | return second 265 | } 266 | } 267 | ``` 268 | ## Data classes 269 | Data class gets component functions, one for each property declared 270 | in the primary constructor, generated automatically, same for all the 271 | other goodies common for data _toString()_, _equals()_, _hashCode()_ and _copy()_. 272 | See http://kotlinlang.org/docs/reference/data-classes.html#data-classes 273 | 274 | ``` kotlin 275 | 276 | data class User(val name: String, val id: Int) 277 | 278 | fun getUser(): User { 279 | return User("Alex", 1) 280 | } 281 | 282 | fun main(args: Array) { 283 | val user = getUser() 284 | println("name = ${user.name}, id = ${user.id}") 285 | 286 | // or 287 | 288 | val (name, id) = getUser() 289 | println("name = $name, id = $id") 290 | 291 | // or 292 | 293 | println("name = ${getUser().component1()}, id = ${getUser().component2()}") 294 | } 295 | ``` 296 | 297 | 298 | ## Data Maps 299 | 300 | Kotlin Standart Library provide component functions for Map.Entry 301 | 302 | ``` kotlin 303 | 304 | fun main(args: Array) { 305 | val map = hashMapOf() 306 | map.put("one", 1) 307 | map.put("two", 2) 308 | 309 | for ((key, value) in map) { 310 | println("key = $key, value = $value") 311 | } 312 | } 313 | ``` 314 | 315 | ## Autogenerated Functions 316 | 317 | Data class gets next functions, generated automatically: 318 | _component functions_, _toString()_, _equals()_, _hashCode()_ and _copy()_. 319 | See http://kotlinlang.org/docs/reference/data-classes.html#data-classes 320 | 321 | ``` kotlin 322 | 323 | data class User(val name: String, val id: Int) 324 | 325 | fun main(args: Array) { 326 | val user = User("Alex", 1) 327 | println(user) // toString() 328 | 329 | val secondUser = User("Alex", 1) 330 | val thirdUser = User("Max", 2) 331 | 332 | println("user == secondUser: ${user == secondUser}") 333 | println("user == thirdUser: ${user == thirdUser}") 334 | 335 | // copy() function 336 | println(user.copy()) 337 | println(user.copy("Max")) 338 | println(user.copy(id = 2)) 339 | println(user.copy("Max", 2)) 340 | } 341 | ``` 342 | 343 | # Delegated properties 344 | ## Custom Delegate 345 | 346 | There's some new syntax: you can say `val 'property name': 'Type' by 'expression'`. 347 | The expression after by is the delegate, because get() and set() methods 348 | corresponding to the property will be delegated to it. 349 | Property delegates don't have to implement any interface, but they have 350 | to provide methods named get() and set() to be called. 351 | 352 | ``` kotlin 353 | 354 | import kotlin.reflect.KProperty 355 | 356 | class Example { 357 | var p: String by Delegate() 358 | 359 | override fun toString() = "Example Class" 360 | } 361 | 362 | class Delegate() { 363 | ): String { 364 | return "$thisRef, thank you for delegating '${prop.name}' to me!" 365 | } 366 | 367 | , value: String) { 368 | println("$value has been assigned to ${prop.name} in $thisRef") 369 | } 370 | } 371 | 372 | fun main(args: Array) { 373 | val e = Example() 374 | println(e.p) 375 | e.p = "NEW" 376 | } 377 | ``` 378 | 379 | ## Lazy property 380 | 381 | Delegates.lazy() is a function that returns a delegate that implements a lazy property: 382 | the first call to get() executes the lambda expression passed to lazy() as an argument 383 | and remembers the result, subsequent calls to get() simply return the remembered result. 384 | If you want thread safety, use blockingLazy() instead: it guarantees that the values will 385 | be computed only in one thread, and that all threads will see the same value. 386 | 387 | ``` kotlin 388 | 389 | class LazySample { 390 | val lazy: String by lazy { 391 | println("computed!") 392 | "my lazy" 393 | } 394 | } 395 | 396 | fun main(args: Array) { 397 | val sample = LazySample() 398 | println("lazy = ${sample.lazy}") 399 | println("lazy = ${sample.lazy}") 400 | } 401 | ``` 402 | 403 | ## Observable Property 404 | 405 | The observable() function takes two arguments: initial value and a handler for modifications. 406 | The handler gets called every time we assign to `name`, it has three parameters: 407 | a property being assigned to, the old value and the new one. If you want to be able to veto 408 | the assignment, use vetoable() instead of observable(). 409 | 410 | ``` kotlin 411 | 412 | import kotlin.properties.Delegates 413 | 414 | class User { 415 | var name: String by Delegates.observable("no name") { 416 | d, old, new -> 417 | println("$old - $new") 418 | } 419 | } 420 | 421 | fun main(args: Array) { 422 | val user = User() 423 | user.name = "Carl" 424 | } 425 | ``` 426 | 427 | ## NotNull property 428 | 429 | Users frequently ask what to do when you have a non-null var, but you don't have an 430 | appropriate value to assign to it in constructor (i.e. it must be assigned later)? 431 | You can't have an uninitialized non-abstract property in Kotlin. You could initialize it 432 | with null, bit then you'd have to check every time you access it. Now you have a delegate 433 | to handle this. If you read from this property before writing to it, it throws an exception, 434 | after the first assignment it works as expected. 435 | 436 | ``` kotlin 437 | 438 | import kotlin.properties.Delegates 439 | 440 | class User { 441 | var name: String by Delegates.notNull() 442 | 443 | fun init(name: String) { 444 | this.name = name 445 | } 446 | } 447 | 448 | fun main(args: Array) { 449 | val user = User() 450 | // user.name -> IllegalStateException 451 | user.init("Carl") 452 | println(user.name) 453 | } 454 | ``` 455 | 456 | ## Properties in map 457 | 458 | Properties stored in a map. This comes up a lot in applications like parsing JSON 459 | or doing other "dynamic" stuff. Delegates take values from this map (by the string keys - 460 | names of properties). Of course, you can have var's as well (add import kotlin.properties.setValue), 461 | that will modify the map upon assignment (note that you'd need MutableMap instead of read-only Map). 462 | 463 | ``` kotlin 464 | 465 | import kotlin.properties.getValue 466 | 467 | class User(val map: Map) { 468 | val name: String by map 469 | val age: Int by map 470 | } 471 | 472 | fun main(args: Array) { 473 | val user = User(mapOf( 474 | "name" to "John Doe", 475 | "age" to 25 476 | )) 477 | 478 | println("name = ${user.name}, age = ${user.age}") 479 | } 480 | ``` 481 | 482 | # Callable references 483 | ## Reference to a function 484 | "Callable References" or "Feature Literals", i.e. an ability to pass 485 | named functions or properties as values. Users often ask 486 | "I have a foo() function, how do I pass it as an argument?". 487 | The answer is: "you prefix it with a `::`". 488 | 489 | 490 | ``` kotlin 491 | 492 | fun main(args: Array) { 493 | val numbers = listOf(1, 2, 3) 494 | println(numbers.filter(::isOdd)) 495 | } 496 | 497 | fun isOdd(x: Int) = x % 2 != 0 498 | ``` 499 | 500 | ## Composition of functions 501 | The composition function return a composition of two functions passed to it: 502 | compose(f, g) = f(g). 503 | Now, you can apply it to callable references. 504 | 505 | 506 | ``` kotlin 507 | 508 | fun main(args: Array) { 509 | val oddLength = compose(::isOdd, ::length) 510 | val strings = listOf("a", "ab", "abc") 511 | println(strings.filter(oddLength)) 512 | } 513 | 514 | fun isOdd(x: Int) = x % 2 != 0 515 | fun length(s: String) = s.length 516 | 517 | fun compose(f: (B) -> C, g: (A) -> B): (A) -> C { 518 | return { x -> f(g(x)) } 519 | } 520 | ``` 521 | 522 | # Longer Examples 523 | ## 99 Botles of Beer 524 | 525 | This example implements the famous "99 Bottles of Beer" program 526 | See http://99-bottles-of-beer.net/ 527 | The point is to print out a song with the following lyrics: 528 | * The "99 bottles of beer" song 529 | * 99 bottles of beer on the wall, 99 bottles of beer. 530 | * Take one down, pass it around, 98 bottles of beer on the wall. 531 | * 98 bottles of beer on the wall, 98 bottles of beer. 532 | * Take one down, pass it around, 97 bottles of beer on the wall. 533 | * ... 534 | * 2 bottles of beer on the wall, 2 bottles of beer. 535 | * Take one down, pass it around, 1 bottle of beer on the wall. 536 | * 1 bottle of beer on the wall, 1 bottle of beer. 537 | * Take one down, pass it around, no more bottles of beer on the wall. 538 | * No more bottles of beer on the wall, no more bottles of beer. 539 | * Go to the store and buy some more, 99 bottles of beer on the wall. 540 | 541 | Additionally, you can pass the desired initial number of bottles to use (rather than 99) 542 | as a command-line argument 543 | ``` kotlin 544 | 545 | package bottles 546 | 547 | fun main(args: Array) { 548 | if (args.isEmpty) { 549 | printBottles(99) 550 | } else { 551 | try { 552 | printBottles(Integer.parseInt(args[0])) 553 | } catch (e: NumberFormatException) { 554 | System.err.println("You have passed '${args[0]}' as a number of bottles, " + 555 | "but it is not a valid integer number") 556 | } 557 | } 558 | } 559 | 560 | fun printBottles(bottleCount: Int) { 561 | if (bottleCount <= 0) { 562 | println("No bottles - no song") 563 | return 564 | } 565 | 566 | println("The \"${bottlesOfBeer(bottleCount)}\" song\n") 567 | 568 | var bottles = bottleCount 569 | while (bottles > 0) { 570 | val bottlesOfBeer = bottlesOfBeer(bottles) 571 | print("$bottlesOfBeer on the wall, $bottlesOfBeer.\nTake one down, pass it around, ") 572 | bottles-- 573 | println("${bottlesOfBeer(bottles)} on the wall.\n") 574 | } 575 | println("No more bottles of beer on the wall, no more bottles of beer.\n" + 576 | "Go to the store and buy some more, ${bottlesOfBeer(bottleCount)} on the wall.") 577 | } 578 | 579 | fun bottlesOfBeer(count: Int): String = 580 | when (count) { 581 | 0 -> "no more bottles" 582 | 1 -> "1 bottle" 583 | else -> "$count bottles" 584 | } + " of beer" 585 | 586 | An excerpt from the Standard Library 587 | 588 | 589 | 590 | // This is an extension property, i.e. a property that is defined for the 591 | // type Array, but does not sit inside the class Array 592 | val Array.isEmpty: Boolean get() = size == 0 593 | ``` 594 | 595 | ## HTML Builder 596 | This is an example of a Type-Safe Groovy-style Builder 597 | Builders are good for declaratively describing data in your code. 598 | In this example we show how to describe an HTML page in Kotlin. 599 | See this page for details: 600 | http://kotlinlang.org/docs/reference/type-safe-builders.html 601 | 602 | ``` kotlin 603 | 604 | package html 605 | 606 | fun main(args: Array) { 607 | val result = 608 | html { 609 | head { 610 | title { +"XML encoding with Kotlin" } 611 | } 612 | body { 613 | h1 { +"XML encoding with Kotlin" } 614 | p { +"this format can be used as an alternative markup to XML" } 615 | 616 | // an element with attributes and text content 617 | a(href = "http://jetbrains.com/kotlin") { +"Kotlin" } 618 | 619 | // mixed content 620 | p { 621 | +"This is some" 622 | b { +"mixed" } 623 | +"text. For more see the" 624 | a(href = "http://jetbrains.com/kotlin") { +"Kotlin" } 625 | +"project" 626 | } 627 | p { +"some text" } 628 | 629 | // content generated from command-line arguments 630 | p { 631 | +"Command line arguments were:" 632 | ul { 633 | for (arg in args) 634 | li { +arg } 635 | } 636 | } 637 | } 638 | } 639 | println(result) 640 | } 641 | 642 | interface Element { 643 | fun render(builder: StringBuilder, indent: String) 644 | } 645 | 646 | class TextElement(val text: String) : Element { 647 | override fun render(builder: StringBuilder, indent: String) { 648 | builder.append("$indent$text\n") 649 | } 650 | } 651 | 652 | abstract class Tag(val name: String) : Element { 653 | val children = arrayListOf() 654 | val attributes = hashMapOf() 655 | 656 | protected fun initTag(tag: T, init: T.() -> Unit): T { 657 | tag.init() 658 | children.add(tag) 659 | return tag 660 | } 661 | 662 | override fun render(builder: StringBuilder, indent: String) { 663 | builder.append("$indent<$name${renderAttributes()}>\n") 664 | for (c in children) { 665 | c.render(builder, indent + " ") 666 | } 667 | builder.append("$indent\n") 668 | } 669 | 670 | private fun renderAttributes(): String? { 671 | val builder = StringBuilder() 672 | for (a in attributes.keys) { 673 | builder.append(" $a=\"${attributes[a]}\"") 674 | } 675 | return builder.toString() 676 | } 677 | 678 | 679 | override fun toString(): String { 680 | val builder = StringBuilder() 681 | render(builder, "") 682 | return builder.toString() 683 | } 684 | } 685 | 686 | abstract class TagWithText(name: String) : Tag(name) { 687 | operator fun String.unaryPlus() { 688 | children.add(TextElement(this)) 689 | } 690 | } 691 | 692 | class HTML() : TagWithText("html") { 693 | fun head(init: Head.() -> Unit) = initTag(Head(), init) 694 | 695 | fun body(init: Body.() -> Unit) = initTag(Body(), init) 696 | } 697 | 698 | class Head() : TagWithText("head") { 699 | fun title(init: Title.() -> Unit) = initTag(Title(), init) 700 | } 701 | 702 | class Title() : TagWithText("title") 703 | 704 | abstract class BodyTag(name: String) : TagWithText(name) { 705 | fun b(init: B.() -> Unit) = initTag(B(), init) 706 | fun p(init: P.() -> Unit) = initTag(P(), init) 707 | fun h1(init: H1.() -> Unit) = initTag(H1(), init) 708 | fun ul(init: UL.() -> Unit) = initTag(UL(), init) 709 | fun a(href: String, init: A.() -> Unit) { 710 | val a = initTag(A(), init) 711 | a.href = href 712 | } 713 | } 714 | 715 | class Body() : BodyTag("body") 716 | class UL() : BodyTag("ul") { 717 | fun li(init: LI.() -> Unit) = initTag(LI(), init) 718 | } 719 | 720 | class B() : BodyTag("b") 721 | class LI() : BodyTag("li") 722 | class P() : BodyTag("p") 723 | class H1() : BodyTag("h1") 724 | 725 | class A() : BodyTag("a") { 726 | public var href: String 727 | get() = attributes["href"]!! 728 | set(value) { 729 | attributes["href"] = value 730 | } 731 | } 732 | 733 | fun html(init: HTML.() -> Unit): HTML { 734 | val html = HTML() 735 | html.init() 736 | return html 737 | } 738 | ``` 739 | ## Game of life 740 | Ommitted 741 | ## Maze 742 | Ommitted 743 | 744 | # Problems 745 | ## Sum 746 | Your task is to implement the sum() function so that it computes the sum of 747 | all elements in the given array a. 748 | 749 | ```kotlin 750 | 751 | package sum 752 | 753 | fun sum(a: IntArray): Int { 754 | var result=0 755 | for (number in a) 756 | result += number 757 | return result 758 | } 759 | ``` 760 | ## indexOfMax 761 | Your task is to implement the indexOfMax() function so that it returns 762 | the index of the largest element in the array, or null if the array is empty. 763 | ```kotlin 764 | 765 | package maxindex 766 | 767 | fun indexOfMax(a: IntArray): Int? { 768 | var maxNumber = Integer.MIN_VALUE 769 | var result = 0 770 | if (a.size==0) 771 | return null 772 | else{ 773 | for (i in a.indices){ 774 | maxNumber = max(maxNumber,a[i]) 775 | if(maxNumber == a[i]) 776 | result = i 777 | } 778 | } 779 | return result 780 | } 781 | fun max(a: Int, b: Int) = if (a > b) a else b 782 | ``` 783 | ## Runs 784 | 785 | Any array may be viewed as a number of "runs" of equal numbers. 786 | For example, the following array has two runs: 1, 1, 1, 2, 2 787 | Three 1's in a row form the first run, and two 2's form the second. 788 | This array has two runs of length one: 3, 4 789 | And this one has five runs: 1, 0, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0 790 | Your task is to implement the runs() function so that it returns the number of runs in the given array. 791 | 792 | ```kotlin 793 | 794 | package runs 795 | 796 | fun runs(a: IntArray): Int { 797 | if (a.size==0) 798 | return 0 799 | 800 | var runNumber =a[0] 801 | var result = 1 802 | 803 | for (number in a) 804 | if(runNumber != number){ 805 | result ++ 806 | runNumber = number 807 | } 808 | return result 809 | } 810 | ``` 811 | ## Pairless 812 | Think of a perfect world where everybody has a soulmate. 813 | Now, the real world is imperfect: there is exactly one number in the array 814 | that does not have a pair. A pair is an element with the same value. 815 | 816 | For example in this array: 817 | 1, 2, 1, 2 818 | every number has a pair, but in this one: 819 | 1, 1, 1 820 | one of the ones is lonely. 821 | 822 | Your task is to implement the findPairless() function so that it finds the 823 | lonely number and returns it. 824 | A hint: there's a solution that looks at each element only once and uses no 825 | data structures like collections or trees. 826 | 827 | ```kotlin 828 | 829 | package pairless 830 | 831 | fun findPairless(a: IntArray): Int { 832 | val distinctElements = a.distinct() 833 | for (element in distinctElements){ 834 | if(!a.count{it == element}.isEven()) 835 | return element 836 | } 837 | return 0 838 | } 839 | fun Int.isEven() = this.mod(2) !=1 840 | ``` 841 | # Canvas 842 | Ommitted 843 | # Koans 844 | ## joinOptions 845 | ```kotlin 846 | 847 | fun joinOptions(options: Collection) = options.joinToString(", ","[","]") 848 | ``` 849 | ## Default arguments 850 | 851 | ```kotlin 852 | 853 | fun foo(name: String, number: Int = 42, toUpperCase: Boolean = false) = 854 | (if (toUpperCase) name.toUpperCase() else name) + number 855 | 856 | fun useFoo() = listOf( 857 | foo("a"), 858 | foo("b", number = 1), 859 | foo("c", toUpperCase = true), 860 | foo(name = "d", number = 2, toUpperCase = true) 861 | ) 862 | ``` 863 | ## Lambdas 864 | ```kotlin 865 | 866 | fun containsEven(collection: Collection): Boolean = collection.any { it.mod(2) == 0 } 867 | ``` 868 | 869 | ## Strings 870 | ```kotlin 871 | 872 | val s = "abc" 873 | val str = "$s.length is ${s.length}" // evaluates to "abc.length is 3" 874 | ... 875 | val month = "(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)" 876 | fun getPattern() = """\d{2} ${month} \d{4}""" 877 | ``` 878 | 879 | ## Data classes 880 | ```kotlin 881 | 882 | data class Person (var name: String, var age: Int) 883 | 884 | fun getPeople(): List { 885 | return listOf(Person("Alice", 29), Person("Bob", 31)) 886 | } 887 | ``` 888 | 889 | ## Nullable types 890 | ```kotlin 891 | 892 | fun sendMessageToClient(client: Client?, message: String?, mailer: Mailer){ 893 | val mail = client?.personalInfo?.email ?: return 894 | if (message!=null) 895 | mailer.sendMessage(mail, message) 896 | } 897 | 898 | class Client (val personalInfo: PersonalInfo?) 899 | class PersonalInfo (val email: String?) 900 | interface Mailer {fun sendMessage(email: String, message: String)} 901 | ``` 902 | 903 | ## Smart Cast 904 | ```kotlin 905 | 906 | fun eval(expr: Expr): Int = 907 | when (expr) { 908 | is Num -> expr.value 909 | is Sum -> eval(expr.left) + eval(expr.right) 910 | else -> throw IllegalArgumentException("Unknown expression") 911 | } 912 | 913 | interface Expr 914 | class Num(val value: Int) : Expr 915 | class Sum(val left: Expr, val right: Expr) : Expr 916 | ``` 917 | 918 | ## Extension Functions 919 | ```kotlin 920 | 921 | fun Int.r(): RationalNumber = RationalNumber(this,1) 922 | fun Pair.r(): RationalNumber = RationalNumber(this.component1(),this.component2()) 923 | 924 | data class RationalNumber(val numerator: Int, val denominator: Int) 925 | ``` 926 | 927 | ## Object expressions 928 | ```kotlin 929 | 930 | import java.util.* 931 | 932 | fun getList(): List { 933 | val arrayList = arrayListOf(1, 5, 2) 934 | Collections.sort(arrayList, object : Comparator { 935 | override fun compare(x: Int, y: Int) = y - x 936 | }) 937 | return arrayList 938 | } 939 | ``` 940 | 941 | ## SAM conversions 942 | ```kotlin 943 | 944 | import java.util.* 945 | 946 | fun getList(): List { 947 | val arrayList = arrayListOf(1, 5, 2) 948 | Collections.sort(arrayList, { x, y -> y-x }) 949 | return arrayList 950 | } 951 | ``` 952 | 953 | ## Extension functions on collections 954 | ```kotlin 955 | 956 | fun getList(): List { 957 | return arrayListOf(1, 5, 2).sortedDescending() 958 | } 959 | ``` 960 | 961 | # Conventions 962 | ## Comparison 963 | ```kotlin 964 | 965 | data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable { 966 | override fun compareTo(other: MyDate) = when { 967 | year != other.year -> year - other.year 968 | month != other.month -> month - other.month 969 | else -> dayOfMonth - other.dayOfMonth 970 | } 971 | } 972 | ``` 973 | 974 | ## In range 975 | ```kotlin 976 | 977 | class DateRange(val start: MyDate, val endInclusive: MyDate){ 978 | operator fun contains(item: MyDate): Boolean = start <= item && item <= endInclusive 979 | } 980 | 981 | 982 | fun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean { 983 | return date in DateRange(first, last) 984 | } 985 | ``` 986 | 987 | 988 | ## 989 | ```kotlin 990 | 991 | operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other) 992 | 993 | class DateRange(override val start: MyDate, override val endInclusive: MyDate): ClosedRange 994 | 995 | data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable{ 996 | override fun compareTo(other: MyDate): Int{ 997 | if(year != other.year) return year - other.year 998 | if(month != other.month) return month - other.month 999 | return dayOfMonth - other.dayOfMonth 1000 | } 1001 | } 1002 | 1003 | fun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean { 1004 | return date in first..last 1005 | } 1006 | ``` 1007 | 1008 | 1009 | ## For loop 1010 | ```kotlin 1011 | 1012 | class DateRange(val start: MyDate, val end: MyDate): Iterable{ 1013 | override fun iterator(): Iterator = DateIterator(this) 1014 | } 1015 | 1016 | class DateIterator(val dateRange:DateRange) : Iterator { 1017 | var current: MyDate = dateRange.start 1018 | override fun next(): MyDate { 1019 | val result = current 1020 | current = current.nextDay() 1021 | return result 1022 | } 1023 | override fun hasNext(): Boolean = current <= dateRange.end 1024 | } 1025 | 1026 | fun iterateOverDateRange(firstDate: MyDate, secondDate: MyDate, handler: (MyDate) -> Unit) { 1027 | for (date in firstDate..secondDate) { 1028 | handler(date) 1029 | } 1030 | } 1031 | ``` 1032 | ## Operators overloading 1033 | 1034 | ```kotlin 1035 | 1036 | operator fun MyDate.plus(timeInterval: TimeInterval) = addTimeIntervals(timeInterval, 1) 1037 | 1038 | class RepeatedTimeInterval(val timeInterval: TimeInterval, val number: Int) 1039 | operator fun TimeInterval.times(number: Int) = RepeatedTimeInterval(this, number) 1040 | 1041 | operator fun MyDate.plus(timeIntervals: RepeatedTimeInterval) = addTimeIntervals(timeIntervals.timeInterval, timeIntervals.number) 1042 | ``` 1043 | 1044 | 1045 | ## Destructuring declarations 1046 | ```kotlin 1047 | 1048 | data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) 1049 | 1050 | fun isLeapDay(date: MyDate): Boolean { 1051 | 1052 | val (year, month, dayOfMonth) = date 1053 | 1054 | // 29 February of a leap year 1055 | return year % 4 == 0 && month == 2 && dayOfMonth == 29 1056 | } 1057 | ``` 1058 | 1059 | ## Invoke 1060 | ```kotlin 1061 | 1062 | class Invokable { 1063 | public var numberOfInvocations: Int = 0 1064 | private set 1065 | operator public fun invoke(): Invokable { 1066 | numberOfInvocations++ 1067 | return this } 1068 | } 1069 | 1070 | fun invokeTwice(invokable: Invokable) = invokable()() 1071 | ``` 1072 | # Collections 1073 | ## Introduction 1074 | Default collections in Kotlin are Java collections, but there are lots of useful extension functions for them. For example, operations that transform a collection to another one, starting with 'to': `toSet` or `toList`. 1075 | 1076 | ```java 1077 | 1078 | data class Shop(val name: String, val customers: List) 1079 | 1080 | data class Customer(val name: String, val city: City, val orders: List) { 1081 | override fun toString() = "$name from ${city.name}" 1082 | } 1083 | 1084 | data class Order(val products: List, val isDelivered: Boolean) 1085 | 1086 | data class Product(val name: String, val price: Double) { 1087 | override fun toString() = "'$name' for $price" 1088 | } 1089 | 1090 | data class City(val name: String) { 1091 | override fun toString() = name 1092 | } 1093 | ``` 1094 | 1095 | ```kotlin 1096 | 1097 | fun Shop.getSetOfCustomers(): Set = this.customers.toSet() 1098 | ``` 1099 | 1100 | ## _filter_ _map_ 1101 | ```kotlin 1102 | 1103 | val numbers = listOf(1, -1, 2) 1104 | numbers.filter { it > 0 } == listOf(1, 2) 1105 | numbers.map { it * it } == listOf(1, 1, 4) 1106 | 1107 | // Return the set of cities the customers are from 1108 | fun Shop.getCitiesCustomersAreFrom(): Set = customers.map {it.city}.toSet() 1109 | 1110 | // Return a list of the customers who live in the given city 1111 | fun Shop.getCustomersFrom(city: City): List = this.customers.filter {it.city == city} 1112 | ``` 1113 | 1114 | ## _all_, _any_ _count_ and _find_ 1115 | ```kotlin 1116 | 1117 | val numbers = listOf(-1, 0, 2) 1118 | val isZero: (Int) -> Boolean = { it == 0 } 1119 | numbers.any(isZero) == true 1120 | numbers.all(isZero) == false 1121 | numbers.count(isZero) == 1 1122 | numbers.find { it > 0 } == 2 1123 | 1124 | // Return true if all customers are from the given city 1125 | fun Shop.checkAllCustomersAreFrom(city: City): Boolean = customers.all{it.city== city} 1126 | 1127 | // Return true if there is at least one customer from the given city 1128 | fun Shop.hasCustomerFrom(city: City): Boolean = customers.any{it.city== city} 1129 | 1130 | // Return the number of customers from the given city 1131 | fun Shop.countCustomersFrom(city: City): Int = customers.count{it.city== city} 1132 | 1133 | // Return a customer who lives in the given city, or null if there is none 1134 | fun Shop.findAnyCustomerFrom(city: City): Customer? = customers.find{it.city== city} 1135 | ``` 1136 | 1137 | ## _flatMap_ 1138 | ```kotlin 1139 | 1140 | val result = listOf("abc", "12").flatMap { it.toCharList() } 1141 | result == listOf('a', 'b', 'c', '1', '2') 1142 | 1143 | // Return all products this customer has ordered 1144 | fun Customer.getOrderedProducts(): Set = orders.flatMap{it.products}.toSet() 1145 | 1146 | // Return all products that were ordered by at least one customer 1147 | fun Shop.getAllOrderedProducts(): Set = customers.flatMap{it.getOrderedProducts()}.toSet() 1148 | 1149 | ``` 1150 | 1151 | ## _max_, _min_, _maxBy_ and _minBy_ 1152 | ```kotlin 1153 | 1154 | listOf(1, 42, 4).max() == 42 1155 | listOf("a", "ab").minBy { it.length } == "a" 1156 | 1157 | // Return a customer whose order count is the highest among all customers 1158 | fun Shop.getCustomerWithMaximumNumberOfOrders(): Customer? = customers.maxBy {it.orders.size} 1159 | 1160 | // Return the most expensive product which has been ordered 1161 | fun Customer.getMostExpensiveOrderedProduct(): Product? = orders.flatMap{it.products}.maxBy{it.price} 1162 | 1163 | ``` 1164 | 1165 | ## _sort_ 1166 | ```kotlin 1167 | 1168 | listOf("bbb", "a", "cc").sorted() == listOf("a", "bbb", "cc") 1169 | listOf("bbb", "a", "cc").sortedBy { it.length } == listOf("a", "cc", "bbb") 1170 | 1171 | // Return a list of customers, sorted by the ascending number of orders they made 1172 | fun Shop.getCustomersSortedByNumberOfOrders(): List = customers.sortedBy{it.orders.size} 1173 | 1174 | ``` 1175 | 1176 | ## _sum_ 1177 | ```kotlin 1178 | 1179 | listOf(1, 5, 3).sum() == 9 1180 | listOf("a", "b", "cc").sumBy { it.length() } == 4 1181 | 1182 | // Return the sum of prices of all products that a customer has ordered. 1183 | // Note: the customer may order the same product for several times. 1184 | fun Customer.getTotalOrderPrice(): Double = orders.flatMap{it.products}.sumByDouble{it.price} 1185 | ``` 1186 | 1187 | ## _groupBy_ 1188 | ```kotlin 1189 | 1190 | val result = listOf("a", "b", "ba", "ccc", "ad").groupBy { it.length() } 1191 | result == mapOf(1 to listOf("a", "b"), 2 to listOf("ba", "ad"), 3 to listOf("ccc")) 1192 | 1193 | // Return a map of the customers living in each city 1194 | fun Shop.groupCustomersByCity(): Map> = customers.groupBy{it.city} 1195 | 1196 | ``` 1197 | 1198 | ## _partition_ 1199 | ```kotlin 1200 | 1201 | val numbers = listOf(1, 3, -4, 2, -11) 1202 | val (positive, negative) = numbers.partition { it > 0 } 1203 | positive == listOf(1, 3, 2) 1204 | negative == listOf(-4, -11) 1205 | 1206 | // Return customers who have more undelivered orders than delivered 1207 | fun Shop.getCustomersWithMoreUndeliveredOrdersThanDelivered(): Set = customers.filter{ 1208 | val (delivered, undelivered) = it.orders.partition { it.isDelivered } 1209 | undelivered.size > delivered.size 1210 | }.toSet() 1211 | 1212 | ``` 1213 | 1214 | ## _fold_ 1215 | ```kotlin 1216 | 1217 | listOf(1, 2, 3, 4).fold(1, { 1218 | partProduct, element -> 1219 | element * partProduct 1220 | }) == 24 1221 | 1222 | fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set { 1223 | val allProducts = customers.flatMap { it.orders.flatMap { it.products }}.toSet() 1224 | return customers.fold(allProducts, { 1225 | orderedByAll, customer -> 1226 | orderedByAll.intersect(customer.orders.flatMap { it.products }.toSet()) 1227 | }) 1228 | } 1229 | 1230 | ``` 1231 | 1232 | ## Compound tasks 1233 | ```kotlin 1234 | 1235 | fun Customer.getMostExpensiveDeliveredProduct(): Product? { 1236 | return orders.filter { it.isDelivered }.flatMap { it.products }.maxBy { it.price } 1237 | } 1238 | 1239 | fun Shop.getNumberOfTimesProductWasOrdered(product: Product): Int { 1240 | return customers.flatMap { it.getOrderedProductsList() }.count { it == product } 1241 | } 1242 | 1243 | fun Customer.getOrderedProductsList(): List { 1244 | return orders.flatMap { it.products } 1245 | } 1246 | ``` 1247 | 1248 | ## Get used to new style 1249 | ```kotlin 1250 | 1251 | fun doSomethingStrangeWithCollection(collection: Collection): Collection? { 1252 | 1253 | val groupsByLength = collection. groupBy { s -> s.length } 1254 | 1255 | val maximumSizeOfGroup = groupsByLength.values.map { group -> group.size }.max() 1256 | 1257 | return groupsByLength.values.firstOrNull { group -> group.size == maximumSizeOfGroup } 1258 | } 1259 | ``` 1260 | # Properties 1261 | ## Properties 1262 | ```kotlin 1263 | 1264 | class PropertyExample() { 1265 | var counter = 0 1266 | var propertyWithCounter: Int? = null 1267 | set(v:Int?) { 1268 | field = v 1269 | counter++ 1270 | } 1271 | } 1272 | 1273 | ``` 1274 | ## Lazy property 1275 | ```kotlin 1276 | 1277 | class LazyProperty(val initializer: () -> Int) { 1278 | var value: Int? = null 1279 | val lazy: Int 1280 | get() { 1281 | if (value == null) { 1282 | value = initializer() 1283 | } 1284 | return value!! 1285 | } 1286 | } 1287 | ``` 1288 | ## Delegates example 1289 | ```kotlin 1290 | 1291 | class LazyProperty(val initializer: () -> Int) { 1292 | val lazyValue: Int by lazy(initializer) 1293 | } 1294 | 1295 | ``` 1296 | ## Delegates how it works 1297 | ```kotlin 1298 | 1299 | 1300 | class EffectiveDate : ReadWriteProperty { 1301 | 1302 | var timeInMillis: Long? = null 1303 | 1304 | override fun getValue(thisRef: R, property: KProperty<*>): MyDate = timeInMillis!!.toDate() 1305 | 1306 | override fun setValue(thisRef: R, property: KProperty<*>, value: MyDate) { 1307 | timeInMillis = value.toMillis() 1308 | } 1309 | } 1310 | ``` 1311 | ## Extension function literals 1312 | A higher-order function is a function that takes functions as parameters, or returns a function. 1313 | ```kotlin 1314 | 1315 | fun task(): List { 1316 | val isEven: Int.() -> Boolean = { this % 2 == 0 } 1317 | val isOdd: Int.() -> Boolean = { this % 2 != 0 } 1318 | 1319 | return listOf(42.isOdd(), 239.isOdd(), 294823098.isEven()) 1320 | } 1321 | ``` 1322 | ## 1323 | ```kotlin 1324 | 1325 | fun buildString(build: StringBuilder.() -> Unit): String { 1326 | val stringBuilder = StringBuilder() 1327 | stringBuilder.build() 1328 | return stringBuilder.toString() 1329 | } 1330 | 1331 | val s = buildString { 1332 | this.append("Numbers: ") 1333 | for (i in 1..3) { 1334 | // 'this' can be omitted 1335 | append(i) 1336 | } 1337 | } 1338 | 1339 | s == "Numbers: 123" 1340 | 1341 | --- 1342 | 1343 | import java.util.HashMap 1344 | 1345 | fun buildMap(build: HashMap.() -> Unit): Map { 1346 | val map = HashMap() 1347 | map.build() 1348 | return map 1349 | } 1350 | 1351 | 1352 | fun usage(): Map { 1353 | return buildMap { 1354 | put(0, "0") 1355 | for (i in 1..10) { 1356 | put(i, "$i") 1357 | } 1358 | } 1359 | } 1360 | 1361 | ``` 1362 | ## The function apply 1363 | ```kotlin 1364 | 1365 | fun T.myApply(f: T.() -> Unit): T { f(); return this } 1366 | 1367 | fun buildString(): String { 1368 | return StringBuilder().myApply { 1369 | append("Numbers: ") 1370 | for (i in 1..10) { 1371 | append(i) 1372 | } 1373 | }.toString() 1374 | } 1375 | 1376 | 1377 | 1378 | fun buildMap(): Map { 1379 | return hashMapOf().myApply { 1380 | put(0, "0") 1381 | for (i in 1..10) { 1382 | put(i, "$i") 1383 | } 1384 | } 1385 | } 1386 | ``` 1387 | ## Html builder 1388 | ```kotlin 1389 | 1390 | fun renderProductTable(): String { 1391 | return html { 1392 | table { 1393 | tr (color = getTitleColor()) { 1394 | td { 1395 | text("Product") 1396 | } 1397 | td { 1398 | text("Price") 1399 | } 1400 | td { 1401 | text("Popularity") 1402 | } 1403 | } 1404 | val products = getProducts() 1405 | for ((index, product) in products.withIndex()) { 1406 | tr { 1407 | td (color = getCellColor(index, 0)) { 1408 | text(product.description) 1409 | } 1410 | td (color = getCellColor(index, 1)) { 1411 | text(product.price) 1412 | } 1413 | td (color = getCellColor(index, 2)) { 1414 | text(product.popularity) 1415 | } 1416 | } 1417 | } 1418 | } 1419 | }.toString() 1420 | } 1421 | 1422 | ``` 1423 | 1424 | ## 1425 | ```kotlin 1426 | 1427 | Look at the questions below and give your answers 1428 | 1429 | 1. In the Kotlin code 1430 | 1431 | tr { 1432 | td { 1433 | text("Product") 1434 | } 1435 | td { 1436 | text("Popularity") 1437 | } 1438 | } 1439 | 'td' is: 1440 | 1441 | a. special built-in syntactic construct 1442 | 1443 | b. function declaration 1444 | 1445 | c. function invocation 1446 | 1447 | --------------------- 1448 | 1449 | 2. In the Kotlin code 1450 | 1451 | tr (color = "yellow") { 1452 | td { 1453 | text("Product") 1454 | } 1455 | td { 1456 | text("Popularity") 1457 | } 1458 | } 1459 | 'color' is: 1460 | 1461 | a. new variable declaration 1462 | 1463 | b. argument name 1464 | 1465 | c. argument value 1466 | 1467 | --------------------- 1468 | 1469 | 3. The block 1470 | 1471 | { 1472 | text("Product") 1473 | } 1474 | from the previous question is: 1475 | 1476 | a. block inside built-in syntax construction td 1477 | 1478 | b. function literal (or "lambda") 1479 | 1480 | c. something mysterious 1481 | 1482 | --------------------- 1483 | 1484 | 4. For the code 1485 | 1486 | tr (color = "yellow") { 1487 | this.td { 1488 | text("Product") 1489 | } 1490 | td { 1491 | text("Popularity") 1492 | } 1493 | } 1494 | which of the following is true: 1495 | 1496 | a. this code doesn't compile 1497 | 1498 | b. this refers to an instance of an outer class 1499 | 1500 | c. this refers to a receiver parameter TR of the function literal: 1501 | 1502 | tr (color = "yellow") { TR.(): Unit -> 1503 | this.td { 1504 | text("Product") 1505 | } 1506 | } 1507 | 1508 | 1 to c, 2 to b, 3 to b, 4 to c 1509 | ``` 1510 | 1511 | ## Generic functions 1512 | ```kotlin 1513 | 1514 | import java.util.* 1515 | 1516 | fun > Collection.partitionTo(first: C, second: C, predicate: (T) -> Boolean): Pair { 1517 | for (element in this) { 1518 | if (predicate(element)) { 1519 | first.add(element) 1520 | } else { 1521 | second.add(element) 1522 | } 1523 | } 1524 | return Pair(first, second) 1525 | } 1526 | 1527 | fun partitionWordsAndLines() { 1528 | val (words, lines) = listOf("a", "a b", "c", "d e"). 1529 | partitionTo(ArrayList(), ArrayList()) { s -> !s.contains(" ") } 1530 | words == listOf("a", "c") 1531 | lines == listOf("a b", "d e") 1532 | } 1533 | 1534 | fun partitionLettersAndOtherSymbols() { 1535 | val (letters, other) = setOf('a', '%', 'r', '}'). 1536 | partitionTo(HashSet(), HashSet()) { c -> c in 'a'..'z' || c in 'A'..'Z'} 1537 | letters == setOf('a', 'r') 1538 | other == setOf('%', '}') 1539 | } 1540 | 1541 | ``` 1542 | -------------------------------------------------------------------------------- /android/kotlin-android.mdown: -------------------------------------------------------------------------------- 1 | _This is my summary of the Kotlin for Android Developers by Antonio Leiva. I use it while learning and as quick reference. It is not intended to be an standalone substitution of the book so if you really want to learn the concepts here presented, buy and read the book and use this repository as a reference and guide._ 2 | 3 | [https://antonioleiva.com/kotlin-android-developers-book/](https://antonioleiva.com/kotlin-android-developers-book/) 4 | 5 | # 4 Classes and functions 6 | 7 | ## 4.1 Class declaration 8 | ```kotlin 9 | 10 | class MainActivity {...} 11 | ``` 12 | Only 1 default constructor 13 | 14 | 15 | ```kotlin 16 | 17 | class Person(name: String, surname: String) 18 | ``` 19 | 20 | Constructor body can be in the `init` 21 | 22 | ```kotlin 23 | 24 | class Person(name: String, surname: String) { 25 | init {...} 26 | } 27 | ``` 28 | 29 | ## 4.2 Class inheritance 30 | 31 | Class are as default `final` 32 | Can only be extended if it's explicitly declared as `open` or `abstract` 33 | 34 | Note that when using the single constructor nomenclature, we need to specify the parameters we're using for the parent constructor. That's the substitution to super() call in Java. 35 | 36 | ```kotlin 37 | 38 | open class Animal(name: String) 39 | class Person(name: String, surname: String) : Animal(name) 40 | ``` 41 | 42 | ## 4.3 Functions 43 | 44 | ```kotlin 45 | 46 | fun onCreate(savedInstanceState: Bundle?) {...} 47 | ``` 48 | ```kotlin 49 | 50 | fun add(x: Int, y: Int) : Int { 51 | return x + y 52 | } 53 | 54 | //or 55 | 56 | fun add(x:Int,y:Int):Int=x+y 57 | ``` 58 | 59 | ## 4.4 Constructor and functions parameters 60 | 61 | First name then type. 62 | ```kotlin 63 | 64 | fun add(x: Int, y: Int) : Int { 65 | return x + y 66 | } 67 | ``` 68 | Default values. 69 | 70 | ```kotlin 71 | 72 | fun toast(message: String, length: Int = Toast.LENGTH_SHORT) { 73 | Toast.makeText(this, message, length).show() 74 | } 75 | 76 | ``` 77 | # 7 Anko and Extension Functions 78 | ## 7.2 Anko 79 | ```kotlin 80 | 81 | import org.jetbrains.anko.find 82 | ... 83 | val forecastList: RecyclerView = find(R.id.forecast_list) 84 | ``` 85 | ## 7.3 Extension functions 86 | 87 | 88 | ```kotlin 89 | 90 | fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) { 91 | Toast.makeText(this, message, duration).show() 92 | } 93 | ``` 94 | 95 | ```kotlin 96 | 97 | public var TextView.text: CharSequence 98 | get() = getText() 99 | set(v) = setText(v) 100 | ``` 101 | # 8 Retrieving data from API 102 | 103 | ```kotlin 104 | 105 | public class Request(val url: String) { 106 | 107 | public fun run() { 108 | val forecastJsonStr = URL(url).readText() 109 | Log.d(javaClass.simpleName, forecastJsonStr) 110 | } 111 | } 112 | ``` 113 | ## 8.2 Performing the request out of the main thread 114 | 115 | If it's used by an Activity, the `uiThread` code won't be executed if `activity.isFinishing()` returns true, and it won't crash if the activity is no longer valid. 116 | 117 | ```kotlin 118 | 119 | async { 120 | Request(url).run() 121 | uiThread { 122 | longToast("Request performed") 123 | } 124 | } 125 | ``` 126 | 127 | Custom executor 128 | 129 | ```kotlin 130 | 131 | val executor = Executors.newScheduledThreadPool(4) 132 | async(executor) {...} 133 | } 134 | ``` 135 | 136 | `async` returns a java Future, in case you want to work with futures. And if you need it to return a future with a result, you can use `asyncResult`. 137 | 138 | # 9 Data Classes 139 | 140 | ```kotlin 141 | 142 | data class Forecast(val date: Date, val temperature: Float, val details: String) 143 | ``` 144 | 145 | * equals() 146 | * hashCode() 147 | * copy() 148 | 149 | ```kotlin 150 | 151 | val f1 = Forecast(Date(), 27.5f, "Shiny day") 152 | val f2 = f1.copy(temperature = 30f) 153 | ``` 154 | 155 | ## 9.2 Mapping 156 | 157 | ```kotlin 158 | 159 | val f1 = Forecast(Date(), 27.5f, "Shiny day") 160 | val (date, temperature, details) = f1 161 | ``` 162 | 163 | ```kotlin 164 | 165 | for ((key, value) in map) { 166 | Log.d("map", "key:$key, value:$value") 167 | } 168 | ``` 169 | 170 | # 10 Parsing data 171 | ## Companion objects 172 | If we need some static properties, constants or functions in a class, we can use a companion object. This object will be shared among all instances of the class, the same as a static field or method would do in Java. 173 | 174 | ```kotlin 175 | 176 | public class ForecastRequest(val zipCode: String) { 177 | companion object { 178 | private val APP_ID = "15646a06818f61f7b8d7823ca833e1ce" 179 | private val URL = "http://api.openweathermap.org/data/2.5/" + 180 | "forecast/daily?mode=json&units=metric&cnt=7" 181 | private val COMPLETE_URL = "$URL&APPID=$APP_ID&q=" 182 | } 183 | 184 | public fun execute(): ForecastResult { 185 | val forecastJsonStr = URL(COMPLETE_URL + zipCode).readText() 186 | return Gson().fromJson(forecastJsonStr, ForecastResult::class.java) 187 | } 188 | } 189 | ``` 190 | ## Domain Layer 191 | This layer will basically implement a set of Commands in charge of performing the tasks in the app. 192 | 193 | ```kotlin 194 | 195 | public interface Command { 196 | fun execute(): T 197 | } 198 | ``` 199 | 200 | ```kotlin 201 | 202 | public class RequestForecastCommand(val zipCode: String) : Command { 203 | override fun execute(): ForecastList { 204 | val forecastRequest = ForecastRequest(zipCode) 205 | return ForecastDataMapper().convertFromDataModel(forecastRequest.execute()) 206 | } 207 | } 208 | ``` 209 | 210 | DataMapper 211 | 212 | ```kotlin 213 | 214 | import com.antonioleiva.weatherapp.data.ForecastResult 215 | import com.antonioleiva.weatherapp.domain.model.Forecast as ModelForecast 216 | 217 | private fun convertForecastListToDomain(list: List): 218 | List { 219 | return list.map { convertForecastItemToDomain(it) } 220 | } 221 | ``` 222 | Command 223 | 224 | ```kotlin 225 | 226 | public class RequestForecastCommand(val zipCode: String) : Command { 227 | override fun execute(): ForecastList { 228 | val forecastRequest = ForecastRequest(zipCode) 229 | return ForecastDataMapper().convertFromDataModel( 230 | forecastRequest.execute()) 231 | } 232 | } 233 | ``` 234 | 235 | With function 236 | 237 | `with` receives an object and an extension function as parameters, and makes the object execute the function. Code inside the brackets acts as an extension function of the object we specify in the first parameter. 238 | 239 | ```kotlin 240 | 241 | override fun onBindViewHolder(holder: ForecastListAdapter.ViewHolder, position: Int) { 242 | with(weekForecast.dailyForecast[position]) { 243 | holder.textView.text = "$date - $description - $high/$low" 244 | } 245 | } 246 | ``` 247 | 248 | # 11 Operator overloading 249 | 250 | ```kotlin 251 | 252 | //From 253 | data class ForecastList(val city: String, val country: String, val dailyForecast:List) 254 | 255 | // To 256 | data class ForecastList(val city: String, val country: String, val dailyForecast: List) { 257 | operator fun get(position: Int): Forecast = dailyForecast[position] 258 | fun size(): Int = dailyForecast.size 259 | } 260 | 261 | ``` 262 | 263 | ```kotlin 264 | 265 | ///From 266 | with(weekForecast.dailyForecast[position]) {... 267 | override fun getItemCount(): Int = weekForecast.dailyForecast.size 268 | 269 | 270 | //To 271 | with(weekForecast[position]) { 272 | override fun getItemCount(): Int = weekForecast.size() 273 | ``` 274 | 275 | ## 11.3 Operators in extension functions 276 | 277 | ```kotlin 278 | 279 | operator fun ViewGroup.get(position: Int): View = getChildAt(position) 280 | 281 | val container: ViewGroup = find(R.id.container) 282 | val view = container[2] 283 | ``` 284 | 285 | # 12 Clickables 286 | 287 | Create onClick. 288 | ```kotlin 289 | 290 | public interface OnItemClickListener { 291 | operator fun invoke(forecast: Forecast) 292 | } 293 | ``` 294 | `invoke` method can be omitted 295 | 296 | ```kotlin 297 | 298 | itemClick.invoke(forecast) 299 | itemClick(forecast) 300 | 301 | ``` 302 | 303 | Call onClick. 304 | 305 | ```kotlin 306 | 307 | forecastList.adapter = 308 | ForecastListAdapter( 309 | result, 310 | object : ForecastListAdapter.OnItemClickListener{ 311 | override fun invoke(forecast: Forecast) { 312 | toast(forecast.date) 313 | } 314 | } 315 | ) 316 | ``` 317 | 318 | 319 | 320 | 321 | Implement onClick 322 | 323 | ```kotlin 324 | 325 | public class ForecastListAdapter( 326 | val weekForecast: ForecastList, 327 | val itemClick: ForecastListAdapter.OnItemClickListener 328 | ): RecyclerView.Adapter() { 329 | 330 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 331 | val view = parent.ctx.layoutInflater.inflate(R.layout.item_forecast, parent, false) 332 | return ViewHolder(view, itemClick) 333 | } 334 | 335 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 336 | holder.bindForecast(weekForecast[position]) 337 | } 338 | 339 | override fun getItemCount(): Int = weekForecast.size() 340 | 341 | class ViewHolder(view: View, val itemClick: OnItemClickListener) : RecyclerView.ViewHolder(view) { 342 | 343 | private val iconView: ImageView 344 | private val dateView: TextView 345 | private val descriptionView: TextView 346 | private val maxTemperatureView: TextView 347 | private val minTemperatureView: TextView 348 | 349 | init { 350 | iconView = view.find(R.id.icon) 351 | dateView = view.find(R.id.date) 352 | descriptionView = view.find(R.id.description) 353 | maxTemperatureView = view.find(R.id.maxTemperature) 354 | minTemperatureView = view.find(R.id.minTemperature) 355 | } 356 | 357 | fun bindForecast(forecast: Forecast) { 358 | with(forecast) { 359 | Picasso.with(itemView.ctx).load(iconUrl).into(iconView) 360 | dateView.text = date 361 | descriptionView.text = description 362 | maxTemperatureView.text = "${high.toString()}º" 363 | minTemperatureView.text = "${low.toString()}º" 364 | itemView.onClick { itemClick(forecast) } 365 | } 366 | } 367 | } 368 | 369 | public interface OnItemClickListener { 370 | operator fun invoke(forecast: Forecast) 371 | } 372 | } 373 | 374 | ``` 375 | 376 | # 13 Lambdas 377 | ## 13.1 Simplifying setOnClickListener() 378 | 379 | ```kotlin 380 | 381 | view.setOnClickListener(object : OnClickListener { 382 | override fun onClick(v: View) { 383 | toast("Click") 384 | } 385 | }) 386 | ``` 387 | Function that receives an interface with a single function can be substituted by the function. 388 | ```kotlin 389 | 390 | fun setOnClickListener(listener: (View) -> Unit) 391 | 392 | view.setOnClickListener({ view -> toast("Click")}) 393 | ``` 394 | 395 | We can even get rid of the left part if the parameters are not being used: 396 | ```kotlin 397 | 398 | view.setOnClickListener({ toast("Click") }) 399 | ``` 400 | 401 | If the function is the last one in the parameters of the function, we can move it out of the parentheses: 402 | 403 | ```kotlin 404 | 405 | view.setOnClickListener() { toast("Click") } 406 | ``` 407 | 408 | And, finally, if the function is the only parameter, we can get rid of the parentheses: 409 | ```kotlin 410 | 411 | view.setOnClickListener { toast("Click") } 412 | ``` 413 | 414 | Anko version 415 | ```kotlin 416 | 417 | view.onClick { toast("Click") } 418 | ``` 419 | 420 | ## 13.2 Click listener for ForecastListAdapter 421 | 422 | ```kotlin 423 | 424 | public class ForecastListAdapter( 425 | val weekForecast: ForecastList, 426 | val itemClick: (Forecast) -> Unit 427 | ) 428 | 429 | class ViewHolder(view: View, val itemClick: (Forecast) -> Unit) 430 | ``` 431 | 432 | Call it from the MainActivity 433 | ```kotlin 434 | 435 | val adapter = ForecastListAdapter(result) { forecast -> toast(forecast.date) } 436 | ``` 437 | 438 | 439 | In functions that only need one parameter, we can make use of the `it` reference. 440 | ```kotlin 441 | 442 | val adapter = ForecastListAdapter(result) { toast(it.date) } 443 | ``` 444 | 445 | ## 13.3 Extending the language 446 | 447 | This function gets an object of type T and a function that will be used as an extension function. The implementation just takes the object and lets it execute the function. 448 | 449 | As the second parameter of the function is another function, it can be brought out of the parentheses, so we can create a block of code where we can use this and the public properties and functions of the object directly: 450 | 451 | ```kotlin 452 | 453 | inline fun with(t: T, body: T.() -> Unit) { t.body() } 454 | ``` 455 | 456 | An **inline function** will be substituted by its code during compilation, instead of really calling to a function 457 | 458 | ```kotlin 459 | 460 | inline fun supportsLollipop(code: () -> Unit) { 461 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 462 | code() 463 | } 464 | 465 | ... 466 | 467 | supportsLollipop { 468 | window.setStatusBarColor(Color.BLACK) 469 | } 470 | 471 | ``` 472 | # 14 Visibility Modifiers 473 | The default modifier in kotlin is `public` 474 | 475 | By default, all constructors are `public` 476 | 477 | ```kotlin 478 | 479 | class C private constructor(a: Int) { ... } 480 | ``` 481 | 482 | # 15 Kotlin Android Extensions 483 | 484 | ```kotlin 485 | 486 | import kotlinx.android.synthetic.view_item.titleView.* 487 | ... 488 | titleView.textView.text = "Hello" 489 | ``` 490 | # 16 Application Singleton and Delegated Properties 491 | ## 16.2 Delegated Properties 492 | 493 | ```kotlin 494 | 495 | class Delegate : ReadWriteProperty { 496 | fun getValue(thisRef: Any?, property: KProperty<*>): T { 497 | return ... 498 | } 499 | 500 | fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 501 | ... 502 | } 503 | } 504 | 505 | 506 | class Example { 507 | var p: String by Delegate() 508 | } 509 | ``` 510 | ## 16.3 Standard Delegates 511 | **Lazy:** It takes a lambda that is executed the first time getValue is called, so the initialisation of the property is delayed up to that moment. 512 | 513 | **Observable:** Execute the lambda expression we specify, every time the set function is called. 514 | 515 | **Vetoable:** Lets you decide whether the value must be saved or not. 516 | 517 | **lateinit:** Identifies that the property should have a non-nullable value, but its assignment will be delayed. If the value is requested before it is assigned, it will throw an exception that clearly identifies the property being accessed. 518 | 519 | **Values from a map:** Creates an instance of an object from a dynamic map. 520 | 521 | ## 16.4 Custom delegate 522 | ```kotlin 523 | 524 | // `object` keyword is used to define singletons.Cannot assign to a variable, but can refer to it by its name. 525 | object DelegatesExt { 526 | fun notNullSingleValue(): 527 | ReadWriteProperty = NotNullSingleValueVar() 528 | } 529 | 530 | private class NotNullSingleValueVar() : ReadWriteProperty { 531 | 532 | private var value: T? = null 533 | 534 | override fun getValue(thisRef: Any?, property: KProperty<*>): T { 535 | return value ?: throw IllegalStateException("${property.name} not initialized") 536 | } 537 | 538 | override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 539 | this.value = if (this.value == null) value 540 | else throw IllegalStateException("${property.name} already initialized") 541 | } 542 | } 543 | ``` 544 | 545 | `instance` will have a reference to the Application, but because we don't know when it is going to be set, we delegate its assignment to the singleton (DelegatesExt) function notNullSingleValue(), that will return its value if it is not null, ioc an exception will be thrown. Initialisation to `instance` will only be performed once. 546 | 547 | ```kotlin 548 | 549 | class App : Application() { 550 | 551 | companion object { 552 | var instance: App by DelegatesExt.notNullSingleValue() 553 | } 554 | 555 | override fun onCreate() { 556 | super.onCreate() 557 | instance = this 558 | } 559 | } 560 | ``` 561 | # 17 Creating an SQLiteOpenHelper 562 | 563 | ```kotlin 564 | 565 | forecastDbHelper.use { 566 | ... 567 | } 568 | ``` 569 | ```kotlin 570 | 571 | public fun use(f: SQLiteDatabase.() -> T): T { 572 | try { 573 | return openDatabase().f() 574 | } finally { 575 | closeDatabase() 576 | } 577 | } 578 | ``` 579 | 580 | ```kotlin 581 | 582 | class ForecastDbHelper(ctx: Context = App.instance) : ManagedSQLiteOpenHelper(ctx, 583 | ForecastDbHelper.DB_NAME, null, ForecastDbHelper.DB_VERSION) { 584 | 585 | companion object { 586 | val DB_NAME = "forecast.db" 587 | val DB_VERSION = 1 588 | val instance: ForecastDbHelper by lazy { ForecastDbHelper() } 589 | } 590 | 591 | override fun onCreate(db: SQLiteDatabase) { 592 | db.createTable(CityForecastTable.NAME, true, 593 | CityForecastTable.ID to INTEGER + PRIMARY_KEY, 594 | CityForecastTable.CITY to TEXT, 595 | CityForecastTable.COUNTRY to TEXT) 596 | 597 | db.createTable(DayForecastTable.NAME, true, 598 | DayForecastTable.ID to INTEGER + PRIMARY_KEY + AUTOINCREMENT, 599 | DayForecastTable.DATE to INTEGER, 600 | DayForecastTable.DESCRIPTION to TEXT, 601 | DayForecastTable.HIGH to INTEGER, 602 | DayForecastTable.LOW to INTEGER, 603 | DayForecastTable.ICON_URL to TEXT, 604 | DayForecastTable.CITY_ID to INTEGER) 605 | } 606 | 607 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { 608 | db.dropTable(CityForecastTable.NAME, true) 609 | db.dropTable(DayForecastTable.NAME, true) 610 | onCreate(db) 611 | } 612 | } 613 | 614 | ``` 615 | ## 17.4 Dependency injection 616 | Simple way. Use default values 617 | 618 | ```kotlin 619 | 620 | class ForecastDbHelper(ctx: Context = App.instance) : 621 | ManagedSQLiteOpenHelper(ctx, ForecastDbHelper.DB_NAME, null, 622 | ForecastDbHelper.DB_VERSION) { 623 | ... 624 | } 625 | 626 | ... 627 | 628 | val dbHelper1 = ForecastDbHelper() // It will use App.instance 629 | val dbHelper2 = ForecastDbHelper(mockedContext) // For tests, for example 630 | 631 | ``` 632 | 633 | # 18 Collections and functional operations 634 | 635 | * **Iterable**: The parent class. Any classes that inherit from this interface represent a sequence of elements we can iterate over. 636 | * **MutableIterable**: Iterables that support removing items during iteration. 637 | * **Collection**: This class represents a generic collection of elements. We get access to functions that return the size of the collection, whether the collection is empty, contains an item or a set of items. All the methods for this kind of collections are only to request data, because 638 | collections are immutable. 639 | * **MutableCollection**: a Collection that supports adding and removing elements. It provides 640 | extra functions such as add, remove or clear among others. 641 | * **List**: Probably the most popular collection type. It represents a generic ordered collection of 642 | elements. As it’s ordered, we can request an item by its position, using the get function. 643 | * **MutableList**: a List that supports adding and removing elements. 644 | * **Set**: an unordered collection of elements that doesn’t support duplicate elements. 645 | * **MutableSet**: a Set that supports adding and removing elements. 646 | * **Map**: a collection of key-value pairs. The keys in a map are unique, which means we cannot 647 | have two pairs with the same key in a map. 648 | * **MutableMap**: a Map that supports adding and removing elements. 649 | 650 | ## 18.1 Aggregate operations 651 | 652 | ### **any** 653 | Returns _true_ if at least one element matches the given predicate. 654 | ```kotlin 655 | 656 | val list = listOf(1, 2, 3, 4, 5, 6) 657 | assertTrue(list.any { it % 2 == 0 }) 658 | assertFalse(list.any { it > 10 }) 659 | ``` 660 | 661 | ### **all** 662 | Returns _true_ if all the elements match the given predicate. 663 | 664 | `assertTrue(list.all { it < 10 })` 665 | 666 | `assertFalse(list.all { it % 2 == 0 })` 667 | 668 | ### **count** 669 | Returns the number of elements matching the given predicate. 670 | 671 | `assertEquals(3, list.count { it % 2 == 0 })` 672 | 673 | ### **fold** 674 | Accumulates the value starting with an initial value and applying an operation from the first to the last element in a collection. 675 | 676 | `assertEquals(25, list.fold(4) { total, next -> total + next })` 677 | 678 | ### **foldRight** 679 | Same as fold, but it goes from the last element to first. 680 | 681 | `assertEquals(25, list.foldRight(4) { total, next -> total + next })` 682 | 683 | ### **forEach** 684 | Performs the given operation to each element. 685 | 686 | `list.forEach { println(it) }` 687 | 688 | ### **forEachIndexed** 689 | 690 | Same as forEach, though we also get the index of the element. 691 | 692 | `list.forEachIndexed { index, value -> println("position $index contains a $value") }` 693 | 694 | ### **max** 695 | Returns the largest element or null if there are no elements. 696 | 697 | `assertEquals(6, list.max())` 698 | 699 | ### **maxBy** 700 | Returns the first element yielding the largest value of the given function or null if there are no elements. 701 | 702 | // The element whose negative is greater 703 | 704 | `assertEquals(1, list.maxBy { -it })` 705 | 706 | ### **min** 707 | Returns the smallest element or null if there are no elements. 708 | 709 | `assertEquals(1, list.min())` 710 | 711 | ### **minBy** 712 | Returns the first element yielding the smallest value of the given function or null if there are no elements. 713 | 714 | // The element whose negative is smaller 715 | 716 | `assertEquals(6, list.minBy { -it })` 717 | 718 | ### **none** 719 | Returns true if no elements match the given predicate. 720 | 721 | // No elements are divisible by 7 722 | 723 | `assertTrue(list.none { it % 7 == 0 })` 724 | 725 | ### **reduce** 726 | Same as fold, but it doesn't use an initial value. It accumulates the value applying an operation from the first to the last element in a collection. 727 | 728 | `assertEquals(21, list.reduce { total, next -> total + next })` 729 | 730 | ### **reduceRight** 731 | 732 | Same as reduce, but it goes from the last element to first. 733 | 734 | `assertEquals(21, list.reduceRight { total, next -> total + next })` 735 | 736 | ### **sumBy** 737 | Returns the sum of all values produced by the transform function from the elements in the collection. 738 | 739 | `assertEquals(3, list.sumBy { it % 2 })` 740 | 741 | 742 | 743 | ## 18.2 Filtering operations 744 | ### **drop** 745 | Returns a list containing all elements except first n elements. 746 | 747 | `assertEquals(listOf(5, 6), list.drop(4))` 748 | 749 | ### **dropWhile** 750 | Returns a list containing all elements except first elements that satisfy the given predicate. 751 | 752 | `assertEquals(listOf(3, 4, 5, 6), list.dropWhile { it < 3 })` 753 | 754 | ### **dropLastWhile** 755 | Returns a list containing all elements except last elements that satisfy the given predicate. 756 | 757 | `assertEquals(listOf(1, 2, 3, 4), list.dropLastWhile { it > 4 })` 758 | 759 | ### **filter** 760 | Returns a list containing all elements matching the given predicate. 761 | 762 | `assertEquals(listOf(2, 4, 6), list.filter { it % 2 == 0 })` 763 | 764 | ### **filterNot** 765 | Returns a list containing all elements not matching the given predicate. 766 | 767 | `assertEquals(listOf(1, 3, 5), list.filterNot { it % 2 == 0 })` 768 | 769 | ### **filterNotNull** 770 | Returns a list containing all elements that are not null. 771 | 772 | `assertEquals(listOf(1, 2, 3, 4), listWithNull.filterNotNull())` 773 | 774 | ### **slice** 775 | Returns a list containing elements at specified indices. 776 | 777 | `assertEquals(listOf(2, 4, 5), list.slice(listOf(1, 3, 4)))` 778 | 779 | ### **take** 780 | Returns a list containing first n elements. 781 | 782 | `assertEquals(listOf(1, 2), list.take(2))` 783 | 784 | ### **takeLast** 785 | Returns a list containing last n elements. 786 | 787 | `assertEquals(listOf(5, 6), list.takeLast(2))` 788 | 789 | ### **takeWhile** 790 | Returns a list containing first elements satisfying the given predicate. 791 | 792 | `assertEquals(listOf(1, 2), list.takeWhile { it < 3 })` 793 | 794 | ## 18.3 Mapping operations 795 | 796 | ### **flatMap** 797 | Iterates over the elements creating a new collection for each one, and finally flattens all the collections into a unique list containing all the elements. 798 | 799 | `assertEquals(listOf(1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7), list.flatMap { listOf(it, it + 1) })` 800 | 801 | ### **groupBy** 802 | Returns a map of the elements in original collection grouped by the result of given function 803 | 804 | `assertEquals(mapOf("odd" to listOf(1, 3, 5), "even" to listOf(2, 4, 6)), list.groupBy { if (it % 2 == 0) "even" else "odd" })` 805 | 806 | ### **map** 807 | Returns a list containing the results of applying the given transform function to each element of the original collection. 808 | 809 | `assertEquals(listOf(2, 4, 6, 8, 10, 12), list.map { it * 2 })` 810 | 811 | ### **mapIndexed** 812 | Returns a list containing the results of applying the given transform function to each element and its index of the original collection. 813 | 814 | `assertEquals(listOf (0, 2, 6, 12, 20, 30), list.mapIndexed { index, it -> index * it })` 815 | 816 | ### **mapNotNull** 817 | Returns a list containing the results of applying the given transform function to each non-null element of the original collection. 818 | 819 | `assertEquals(listOf(2, 4, 6, 8), listWithNull.mapNotNull { it * 2 })` 820 | 821 | 822 | ## 18.4 Elements operations 823 | ### **contains** 824 | Returns true if the element is found in the collection. 825 | 826 | `assertTrue(list.contains(2))` 827 | 828 | ### **elementAt** 829 | Returns an element at the given index or throws an IndexOutOfBoundsException if the index is out of bounds of this collection. 830 | 831 | `assertEquals(2, list.elementAt(1))` 832 | 833 | ### **elementAtOrElse** 834 | Returns an element at the given index or the result of calling the default function if the index is out of bounds of this collection. 835 | 836 | `assertEquals(20, list.elementAtOrElse(10, { 2 * it }))` 837 | 838 | ### **elementAtOrNull** 839 | Returns an element at the given index or null if the index is out of bounds of this collection. 840 | 841 | `assertNull(list.elementAtOrNull(10))` 842 | 843 | ### **first** 844 | Returns the first element matching the given predicate 845 | 846 | `assertEquals(2, list.first { it % 2 == 0 })` 847 | 848 | ### **firstOrNull** 849 | Returns the first element matching the given predicate, or null if no element was found. 850 | 851 | `assertNull(list.firstOrNull { it % 7 == 0 })` 852 | 853 | ### **indexOf** 854 | Returns the first index of element, or -1 if the collection does not contain element. 855 | 856 | `assertEquals(3, list.indexOf(4))` 857 | 858 | ### **indexOfFirst** 859 | Returns index of the first element matching the given predicate, or -1 if the collection does not contain such element. 860 | 861 | `assertEquals(1, list.indexOfFirst { it % 2 == 0 })` 862 | 863 | ### **indexOfLast** 864 | Returns index of the last element matching the given predicate, or -1 if the collection does not contain such element. 865 | 866 | `assertEquals(5, list.indexOfLast { it % 2 == 0 })` 867 | 868 | ### **last** 869 | Returns the last element matching the given predicate. 870 | 871 | `assertEquals(6, list.last { it % 2 == 0 })` 872 | 873 | ### **lastIndexOf** 874 | Returns last index of element, or -1 if the collection does not contain an element. 875 | 876 | ```kotlin 877 | 878 | val listRepeated = listOf(2, 2, 3, 4, 5, 5, 6) 879 | assertEquals(5, listRepeated.lastIndexOf(5)) 880 | ``` 881 | ### **lastOrNull** 882 | Returns the last element matching the given predicate, or null if no such element was found. 883 | 884 | ```kotlin 885 | 886 | val list = listOf(1, 2, 3, 4, 5, 6) 887 | assertNull(list.lastOrNull { it % 7 == 0 }) 888 | ``` 889 | 890 | ### **single** 891 | Returns the single element matching the given predicate, or throws exception if there is no or more than one matching element. 892 | 893 | `assertEquals(5, list.single { it % 5 == 0 })` 894 | 895 | ### **singleOrNull** 896 | Returns the single element matching the given predicate, or null if element was not found or more than one element was found. 897 | 898 | `assertNull(list.singleOrNull { it % 7 == 0 })` 899 | 900 | ## 18.5 Generation operations 901 | ### **merge** 902 | Returns a list of values built from elements of both collections with same indexes using the provided transform function. The list has the length of shortest collection. 903 | 904 | ```kotlin 905 | 906 | val list = listOf(1, 2, 3, 4, 5, 6) 907 | val listRepeated = listOf(2, 2, 3, 4, 5, 5, 6) 908 | assertEquals(listOf(3, 4, 6, 8, 10, 11), list.merge(listRepeated) { it1, it2 -> it1 + it2 }) 909 | ``` 910 | 911 | ### **partition** 912 | Splits original collection into pair of collections, where the first collection contains elements for which the predicate returned true, while the second collection contains elements for which the predicate returned false. 913 | 914 | `assertEquals(Pair(listOf(2, 4, 6), listOf(1, 3, 5)), list.partition { it % 2 == 0 })` 915 | 916 | ### **plus** 917 | Returns a list containing all elements of the original collection and then all elements of the given collection. Because of the name of the function, we can use the ‘+’ operator with it. 918 | 919 | `assertEquals(listOf(1, 2, 3, 4, 5, 6, 7, 8), list + listOf(7, 8))` 920 | 921 | ### **zip** 922 | Returns a list of pairs built from the elements of both collections with the same indexes. The list has the length of the shortest collection. 923 | 924 | `assertEquals(listOf(Pair(1, 7), Pair(2, 8)), list.zip(listOf(7, 8)))` 925 | 926 | ### **unzip** 927 | Generates a Pair of Lists from a List of Pairs 928 | 929 | `assertEquals(Pair(listOf(5, 6), listOf(7, 8)), listOf(Pair(5, 7), Pair(6, 8)).unzip())` 930 | 931 | ## 18.6 Ordering operations 932 | ### **reverse** 933 | Returns a list with elements in reversed order. 934 | ```kotlin 935 | 936 | val unsortedList = listOf(3, 2, 7, 5) 937 | assertEquals(listOf(5, 7, 2, 3), unsortedList.reverse()) 938 | ``` 939 | 940 | ### **sort** 941 | Returns a sorted list of all elements. 942 | 943 | `assertEquals(listOf(2, 3, 5, 7), unsortedList.sort())` 944 | 945 | ### **sortBy** 946 | Returns a list of all elements, sorted by the specified comparator. 947 | 948 | `assertEquals(listOf(3, 7, 2, 5), unsortedList.sortBy { it % 3 })` 949 | 950 | ### **sortDescending** 951 | Returns a sorted list of all elements, in descending order. 952 | 953 | `assertEquals(listOf(7, 5, 3, 2), unsortedList.sortDescending())` 954 | 955 | ### **sortDescendingBy** 956 | Returns a sorted list of all elements, in descending order by the results of the specified order function. 957 | 958 | `assertEquals(listOf(2, 5, 7, 3), unsortedList.sortDescendingBy { it % 3 })` 959 | 960 | ## 19 DataBase 961 | 962 | **Model Class for the database.** 963 | 964 | Use of delegate `by map` to automatically convert from map to database model. 965 | 966 | ```kotlin 967 | 968 | class CityForecast(val map: MutableMap, val dailyForecast: List) { 969 | var _id: Long by map 970 | var city: String by map 971 | var country: String by map 972 | 973 | //Second constructor with an empty Map and the data as parameters. 974 | constructor(id: Long, city: String, country: String, dailyForecast: List) 975 | : this(HashMap(), dailyForecast) { // Here is the return statement. the super method (the main constructor) with an new empty HashMap, and the dailyForecast. 976 | this._id = id 977 | this.city = city 978 | this.country = country 979 | } 980 | } 981 | ``` 982 | 983 | **Writing and requesting data** 984 | We use a DbHelper and a data mapper. 985 | 986 | ```kotlin 987 | 988 | class ForecastDb(val forecastDbHelper: ForecastDbHelper = ForecastDbHelper.instance, 989 | val dataMapper: DbDataMapper = DbDataMapper()) { 990 | 991 | ``` 992 | We call the `use` function to get the data from the database. 993 | 994 | **Request** 995 | ```kotlin 996 | 997 | fun requestForecastByZipCode(zipCode: Long, date: Long) = forecastDbHelper.use { 998 | 999 | val dailyRequest = "${DayForecastTable.CITY_ID} = ? AND ${DayForecastTable.DATE} >= ?" 1000 | val dailyForecast = select(DayForecastTable.NAME) 1001 | .whereSimple(dailyRequest, zipCode.toString(), date.toString()) 1002 | .parseList { DayForecast(HashMap(it)) } 1003 | 1004 | val city = select(CityForecastTable.NAME) 1005 | .whereSimple("${CityForecastTable.ID} = ?", zipCode.toString()) 1006 | .parseOpt { CityForecast(HashMap(it), dailyForecast) } 1007 | 1008 | if (city != null) dataMapper.convertToDomain(city) else null 1009 | } 1010 | ``` 1011 | **Save** 1012 | 1013 | ```kotlin 1014 | 1015 | fun saveForecast(forecast: ForecastList) = forecastDbHelper.use { 1016 | 1017 | clear(CityForecastTable.NAME) 1018 | clear(DayForecastTable.NAME) 1019 | 1020 | with(dataMapper.convertFromDomain(forecast)) { 1021 | insert(CityForecastTable.NAME, *map.toVarargArray()) 1022 | dailyForecast.forEach { insert(DayForecastTable.NAME, *it.map.toVarargArray()) } 1023 | } 1024 | } 1025 | ``` 1026 | 1027 | The _RowParser_ relies on the order of the columns, while the _MapRowParser_ uses the name of the column as the key of the map. 1028 | 1029 | ## 20 Null safety in Kotlin 1030 | 1031 | ```kotlin 1032 | 1033 | val a : Int? = null 1034 | // can not be used. Not compile. 1035 | a.toString() 1036 | 1037 | //Smart Cast: now is a non nullable type 1038 | if (a != null) 1039 | a.toString() // Compile 1040 | 1041 | // Safe call operator 1042 | a?.toString() // If `a` is null it won't do nothing. 1043 | 1044 | //Alternative values. Elvis operator. 1045 | val myString = a?.toString ?: "" 1046 | 1047 | // Also works 1048 | val myString = a?.toString() ?: return false 1049 | val myString = a?.toString() ?: throw IllegalStateException() 1050 | 1051 | // Force to be not nullabe "CAREFULL" 1052 | a!!.toString() 1053 | ``` 1054 | 1055 | Objects coming from Java will be marked with `!`. The developer should choose how to behave (Nullable or not Nullable) 1056 | 1057 | We decide 1058 | ```kotlin 1059 | 1060 | override fun onCreate(savedInstanceState: Bundle) { ... } 1061 | // This is the correct one. We know that 'Bundle' could be null. 1062 | override fun onCreate(savedInstanceState: Bundle?) { ... } 1063 | 1064 | 1065 | ``` 1066 | 1067 | 1068 | _@Nullable_ and _@NotNull_ Android annotations can help. Jetbrains also has its own _@Nullable_ annotation. 1069 | 1070 | ## 21. Business Logic 1071 | 1072 | Create an interface that the data providers must implement. 1073 | ```kotlin 1074 | 1075 | interface ForecastDataSource { 1076 | fun requestForecastByZipCode(zipCode: Long, date: Long): ForecastList? 1077 | } 1078 | ``` 1079 | 1080 | The provider iterate through the data sources until it gets some data. Repository Pattern 1081 | 1082 | ```kotlin 1083 | 1084 | class ForecastProvider(val sources: List = ForecastProvider.SOURCES) { 1085 | 1086 | companion object { 1087 | val DAY_IN_MILLIS = 1000 * 60 * 60 * 24; 1088 | val SOURCES = listOf(ForecastDb(), ForecastServer()) 1089 | } 1090 | 1091 | fun requestByZipCode(zipCode: Long, days: Int): ForecastList 1092 | = sources.firstResult { requestSource(it, days, zipCode) } 1093 | 1094 | private fun requestSource(source: ForecastDataSource, days: Int, zipCode: Long): ForecastList? { 1095 | val res = source.requestForecastByZipCode(zipCode, todayTimeSpan()) 1096 | return if (res != null && res.size() >= days) res else null 1097 | } 1098 | 1099 | private fun todayTimeSpan() = System.currentTimeMillis() / DAY_IN_MILLIS * DAY_IN_MILLIS 1100 | } 1101 | ``` 1102 | 1103 | ## 22. Flow control and ranges 1104 | 1105 | **if** 1106 | The if expression always returns a value. Unit. 1107 | ```kotlin 1108 | 1109 | val z = if (condition) x else y 1110 | ``` 1111 | 1112 | **when** 1113 | ```kotlin 1114 | 1115 | val result = when (x) { 1116 | 0, 1 -> "binary" 1117 | else -> "error" 1118 | } 1119 | ``` 1120 | Casting 1121 | ``` kotlin 1122 | 1123 | when(view) { 1124 | is TextView -> view.setText("I'm a TextView") 1125 | is EditText -> toast("EditText value: ${view.getText()}") 1126 | is ViewGroup -> toast("Number of children: ${view.getChildCount()} ") 1127 | else -> view.visibility = View.GONE 1128 | } 1129 | ``` 1130 | Ranges 1131 | ```kotlin 1132 | 1133 | val cost = when(x) { 1134 | in 1..10 -> "cheap" 1135 | in 10..100 -> "regular" 1136 | in 100..1000 -> "expensive" 1137 | in specialValues -> "special value!" 1138 | else -> "not rated" 1139 | } 1140 | 1141 | ``` 1142 | Mixed 1143 | ```kotlin 1144 | 1145 | val res = when { 1146 | x in 1..10 -> "cheap" 1147 | s.contains("hello") -> "it's a welcome!" 1148 | v is ViewGroup -> "child count: ${v.getChildCount()}" 1149 | else -> "" 1150 | } 1151 | ``` 1152 | 1153 | **for** 1154 | 1155 | ```kotlin 1156 | 1157 | for (item in collection) { 1158 | print(item) 1159 | } 1160 | ``` 1161 | Ranges 1162 | ```kotlin 1163 | 1164 | for (index in 0..viewGroup.getChildCount() - 1) { 1165 | val view = viewGroup.getChildAt(index) 1166 | view.visibility = View.VISIBLE 1167 | } 1168 | ``` 1169 | Indices in lists and arrays 1170 | ```kotlin 1171 | 1172 | for (i in array.indices) 1173 | print(array[i]) 1174 | 1175 | ``` 1176 | 1177 | **while** 1178 | 1179 | ```kotlin 1180 | 1181 | while (x > 0) { 1182 | x-- 1183 | } 1184 | ``` 1185 | **do while** 1186 | ```kotlin 1187 | 1188 | do { 1189 | val y = retrieveData() 1190 | } while (y != null) // y is visible here! 1191 | ``` 1192 | 1193 | **Ranges** 1194 | 1195 | ```kotlin 1196 | 1197 | if (i in 0..10) 1198 | println(i) 1199 | ``` 1200 | 1201 | Ranges are incremental by default 1202 | ```kotlin 1203 | 1204 | for (i in 10..0) 1205 | println(i)//Would do nothing 1206 | ``` 1207 | 1208 | _downTo_ 1209 | ```kotlin 1210 | 1211 | for (i in 10 downTo 0) 1212 | println(i) 1213 | ``` 1214 | 1215 | _step_ 1216 | ```kotlin 1217 | 1218 | for (i in 1..4 step 2) println(i) 1219 | for (i in 4 downTo 1 step 2) println(i) 1220 | ``` 1221 | 1222 | _until_ 1223 | ```kotlin 1224 | 1225 | for (i in 0 until 4) println(i)// 0 until 4 == 0..3 1226 | ``` 1227 | 1228 | Cleaner: `(i in 0 until list.size)` instead of `(i in 0..list.size - 1)` 1229 | 1230 | Creative samples 1231 | ```kotlin 1232 | 1233 | val views = (0..viewGroup.childCount - 1).map { viewGroup.getChildAt(it) } 1234 | ``` 1235 | 1236 | 1237 | ## 23 _reified_ 1238 | As you may know, when we create a generic method in Java, there is no way to get the class from the generic type. A popular workaround is to pass the class as a parameter. In Kotlin, an inline function can be reified, which means we can get and use the class of the generic type inside the function. In this case, we can create the intent inside the function, by calling T::class.javaClass. 1239 | 1240 | ```kotlin 1241 | 1242 | public inline fun Context.startActivity( 1243 | vararg params: Pair) { 1244 | val intent = Intent(this, T::class.javaClass) 1245 | params forEach { intent.putExtra(it.first, it.second) } 1246 | startActivity(intent) 1247 | } 1248 | ``` 1249 | 1250 | It creates an intent by getting the javaClass from the generic type, iterates over params and adds them to the intent, and starts the activity using the intent. The reified type is limited to be an Activity descendant. 1251 | 1252 | ## 24 Interfaces 1253 | ```kotlin 1254 | 1255 | interface FlyingAnimal { 1256 | fun fly() 1257 | } 1258 | 1259 | class Bird : FlyingAnimal { 1260 | val wings: Wings = Wings() 1261 | override fun fly() = wings.move() 1262 | } 1263 | 1264 | class Bat : FlyingAnimal { 1265 | val wings: Wings = Wings() 1266 | override fun fly() = wings.move() 1267 | } 1268 | ``` 1269 | 1270 | But in Kotlin we can implementation functions in the interface. 1271 | 1272 | The only difference from a class is that they are stateless, so the properties that need a backing field will need to be overridden by the class. The class will be in charge of saving the state of interface properties. 1273 | 1274 | 1275 | ```kotlin 1276 | 1277 | interface FlyingAnimal { 1278 | val wings: Wings 1279 | fun fly() = wings.move() 1280 | } 1281 | 1282 | class Bird : FlyingAnimal { 1283 | override val wings: Wings = Wings() 1284 | } 1285 | 1286 | class Bat : FlyingAnimal { 1287 | override val wings: Wings = Wings() 1288 | } 1289 | 1290 | // Use 1291 | val bird = Bird() 1292 | val bat = Bat() 1293 | 1294 | bird.fly() 1295 | bat.fly() 1296 | ``` 1297 | 1298 | **Delegation Pattern** 1299 | ```kotlin 1300 | 1301 | interface CanFly { 1302 | fun fly() 1303 | } 1304 | // 'Bird' class implements 'CanFly' by the parameter passed 1305 | class Bird(f: CanFly) : CanFly by f 1306 | 1307 | // class 'AnimalWithWings' is an implementation of 'CanFly' 1308 | class AnimalWithWings : CanFly { 1309 | val wings: Wings = Wings() 1310 | override fun fly() = wings.move() 1311 | } 1312 | 1313 | // 'birdWithWings' is an instance of 'Bird' whose 'CanFly' implementation is 'AnimalWithWings' 1314 | val birdWithWings = Bird(AnimalWithWings()) 1315 | birdWithWings.fly() 1316 | ``` 1317 | 1318 | But now wings can be used with another animals that are not birds. 1319 | 1320 | ```kotlin 1321 | 1322 | class Bat : CanFly by AnimalWithWings() 1323 | ... 1324 | val bat = Bat() 1325 | bat.fly() 1326 | ``` 1327 | 1328 | ## 25 Generics 1329 | 1330 | ```kotlin 1331 | 1332 | class TypedClass(parameter: T) { 1333 | val value: T = parameter 1334 | } 1335 | ``` 1336 | Use 1337 | ```kotlin 1338 | 1339 | val t1 = TypedClass("Hello World!") 1340 | val t2 = TypedClass(25) 1341 | ``` 1342 | 1343 | Infer Types 1344 | 1345 | ```kotlin 1346 | 1347 | val t1 = TypedClass("Hello World!") 1348 | val t2 = TypedClass(25) 1349 | val t3 = TypedClass(null) // This receiving a null reference, the type still needs to be specified because it can’t be inferred. 1350 | 1351 | ``` 1352 | 1353 | Restrictions 1354 | 1355 | ```kotlin 1356 | 1357 | // Not Null 1358 | class TypedClass(parameter: T) { 1359 | val value: T = parameter 1360 | } 1361 | 1362 | //Only Contexts 1363 | class TypedClass(parameter: T) { 1364 | val value: T = parameter 1365 | } 1366 | ``` 1367 | 1368 | Generic Functions 1369 | 1370 | ```kotlin 1371 | 1372 | fun typedFunction(item: T): List { 1373 | ... 1374 | } 1375 | ``` 1376 | 1377 | ### Kotlin Generic Helpers 1378 | **let** 1379 | It receives a function with the object caller as a parameter, and returns function returns value. 1380 | Useful to deal with nullable objects. 1381 | 1382 | _Implementation_ 1383 | ```kotlin 1384 | 1385 | inline fun T.let(f: (T) -> R): R = f(this) 1386 | ``` 1387 | _Use_ 1388 | ```kotlin 1389 | 1390 | //Change this 1391 | if (forecast != null) dataMapper.convertDayToDomain(forecast) else null 1392 | // to this 1393 | forecast?.let { dataMapper.convertDayToDomain(it) } 1394 | ``` 1395 | 1396 | **with** 1397 | Receives an object, and a function that will be executed as an extension function. 1398 | Returns an object defined in the last line of the function. 1399 | 1400 | _Implementation_ 1401 | ```kotlin 1402 | 1403 | inline fun with(receiver: T, f: T.() -> R): R = receiver.f() 1404 | ``` 1405 | _Use_ 1406 | ```kotlin 1407 | 1408 | fun convertFromDomain(forecast: ForecastList) = with(forecast) { 1409 | val daily = dailyForecast.map { convertDayFromDomain(id, it) } 1410 | CityForecast(id, city, country, daily) 1411 | } 1412 | ``` 1413 | 1414 | **apply** 1415 | Can be used to avoid the creation of builders, because the object that calls the function can initialise itself the way it needs, and the apply function will return the same object. 1416 | 1417 | _Implementation_ 1418 | ```kotlin 1419 | 1420 | inline fun T.apply(f: T.() -> Unit): T { f(); return this } 1421 | ``` 1422 | _Use_ 1423 | ```kotlin 1424 | 1425 | val textView = TextView(context).apply { 1426 | text = "Hello" 1427 | hint = "Hint" 1428 | textColor = android.R.color.white 1429 | } 1430 | ``` 1431 | 1432 | ```kotlin 1433 | 1434 | //Change this 1435 | private fun createUpDrawable() = with(DrawerArrowDrawable(toolbar.ctx)) { 1436 | progress = 1f 1437 | this 1438 | } 1439 | //to this 1440 | private fun createUpDrawable() = DrawerArrowDrawable(toolbar.ctx).apply { 1441 | progress = 1f 1442 | } 1443 | #Extra 1444 | 1445 | ## Extend Instrumetation Tests 1446 | 1447 | ```kotlin 1448 | 1449 | @Test fun modifyZipCode_changesToolbarTitle() { 1450 | openActionBarOverflowOrOptionsMenu(activityRule.activity) 1451 | onView(withText(R.string.settings)).perform(click()) 1452 | onView(withId(R.id.cityCode)).perform(replaceText("28830")) 1453 | pressBack() 1454 | onView(isAssignableFrom(Toolbar::class.java)) 1455 | .check(matches(withToolbarTitle(`is`("San Fernando de Henares (ES)")))) 1456 | } 1457 | 1458 | private fun withToolbarTitle(textMatcher: Matcher): Matcher = 1459 | object : BoundedMatcher(Toolbar::class.java) { 1460 | 1461 | override fun matchesSafely(toolbar: Toolbar): Boolean { 1462 | return textMatcher.matches(toolbar.title) 1463 | } 1464 | 1465 | override fun describeTo(description: Description) { 1466 | description.appendText("with toolbar title: ") 1467 | textMatcher.describeTo(description) 1468 | } 1469 | } 1470 | ``` 1471 | 1472 | ## Nested Classes 1473 | Not able to access the members of the outter class. 1474 | ```kotlin 1475 | 1476 | class Outer { 1477 | private val bar: Int = 1 1478 | class Nested { 1479 | fun foo() = 2 1480 | } 1481 | } 1482 | 1483 | val demo = Outer.Nested().foo() // == 2 1484 | ``` 1485 | **Inner** To access to the members of the outer class, 1486 | ```kotlin 1487 | 1488 | class Outer { 1489 | private val bar: Int = 1 1490 | inner class Inner { 1491 | fun foo() = bar 1492 | } 1493 | } 1494 | 1495 | val demo = Outer().Inner().foo() // == 1 1496 | ``` 1497 | 1498 | ## Enum classes 1499 | ```kotlin 1500 | 1501 | enum class Day { 1502 | SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY 1503 | } 1504 | ``` 1505 | With parameters 1506 | ```kotlin 1507 | 1508 | enum class Icon(val res: Int) { 1509 | UP(R.drawable.ic_up), 1510 | SEARCH(R.drawable.ic_search), 1511 | CAST(R.drawable.ic_cast) 1512 | } 1513 | 1514 | val searchIconRes = Icon.SEARCH.res 1515 | ``` 1516 | Requested by the String 1517 | ```kotlin 1518 | 1519 | val search: Icon = Icon.valueOf("SEARCH") 1520 | ``` 1521 | Get an Array 1522 | ```kotlin 1523 | 1524 | val iconList: Array = Icon.value 1525 | ``` 1526 | 1527 | Obtain its name and the position 1528 | ```kotlin 1529 | 1530 | val searchName: String = Icon.SEARCH.name 1531 | val searchPosition: Int = Icon.SEARCH.ordinal 1532 | ``` 1533 | 1534 | ## Sealed classes 1535 | Sealed classes are used for representing restricted class hierarchies. 1536 | The number of classes that extend a sealed class is fixed. 1537 | ```kotlin 1538 | 1539 | sealed class Option { 1540 | class Some : Option() 1541 | object None : Option() 1542 | } 1543 | 1544 | val result = when (option) { 1545 | is Option.Some<*> -> "Contains a value" 1546 | is Option.None -> "Empty" 1547 | } 1548 | ``` 1549 | 1550 | ## Exceptions 1551 | In Kotlin, all exceptions implement Throwable, have a message and are unchecked. 1552 | To be unchecked allow us not to use try/catch. 1553 | Both _try_ and _throw_ can beassigned to variables. 1554 | 1555 | ```kotlin 1556 | 1557 | val s = when(x){ 1558 | is Int -> "Int instance" 1559 | is String -> "String instance" 1560 | else -> throw UnsupportedOperationException("Not valid type") 1561 | } 1562 | // or 1563 | val s = try { x as String } catch(e: ClassCastException) { null } 1564 | ``` --------------------------------------------------------------------------------