$30 off During Our Annual Pro Sale. View Details »

Kotlin Advanced Tricks

Kotlin Advanced Tricks

Presentation about some advanced Kotlin tips, given on

- iMasters Kotlin Summit (September/2018)

Ubiratan Soares
PRO

September 15, 2018
Tweet

More Decks by Ubiratan Soares

Other Decks in Programming

Transcript

  1. ADVANCED
    KOTLIN TRICKS
    Ubiratan Soares
    September / 2018

    View Slide

  2. STDLIB :
    THE FORGOTTEN
    FUNCTIONS

    View Slide

  3. Repeat
    fun severalPrints() =
    repeat(times = 10) {
    print("Hey!")
    }

    View Slide

  4. /!"
    * Calls the specified function [block] with `this` value as its
    * argument and returns `this` value.
    #$
    @kotlin.internal.InlineOnly
    @SinceKotlin("1.1")
    public inline fun T.also(block: (T) %& Unit): T {
    contract {
    callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
    }
    Also

    View Slide

  5. fun validate(name: String) =
    name.length in 2'(20
    fun saveUser(name: String) =
    validate(name)
    .also { saveWith(name) }

    View Slide

  6. TakeIf
    /!"
    * Returns `this` value if it satisfies the
    * given [predicate] or `null`, if it doesn't.
    #$
    @kotlin.internal.InlineOnly
    @SinceKotlin("1.1")
    public inline fun T.takeIf(predicate: (T) %& Boolean): T? {
    contract {
    callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
    }

    View Slide

  7. TakeIf
    /!"
    * Returns `this` value if it satisfies the
    * given [predicate] or `null`, if it doesn't.
    #$
    @kotlin.internal.InlineOnly
    @SinceKotlin("1.1")
    public inline fun T.takeIf(predicate: (T) %& Boolean): T? {
    contract {
    callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
    }

    View Slide

  8. TakeUnless
    /!"
    * Returns `this` value if it _does not_ satisfy
    * the given [predicate] or `null`, if it does.
    #$
    @kotlin.internal.InlineOnly
    @SinceKotlin("1.1")
    public inline fun T.takeUnless(predicate: (T) %& Boolean): T? {
    contract {
    callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
    }

    View Slide

  9. fun names() =
    if (Random().nextBoolean()) "Elvis"
    else "Presley"
    val shortName =
    names()
    .takeIf { it.length )* 5 }
    +, "Too long"
    val longName =
    names()
    .takeUnless { it.length )* 5 }
    +, "Too Short"

    View Slide

  10. INVOKING
    LIKE A PRO

    View Slide

  11. class DeepLinkParser {
    fun parse(uri : String) : ProductId {
    // Parse your URI and extract product id
    }
    }

    View Slide

  12. class DeepLinkHandler(
    val parser: DeepLinkParser) {
    operator fun invoke(url : String) : Intent {
    // Handle with parser and get your Intent
    }
    }

    View Slide

  13. class DeepLinkHandler(
    val parser: DeepLinkParser) {
    operator fun invoke(url : String) : Intent {
    // Handle with parser and get your Intent
    }
    }

    View Slide

  14. Invoking Instances
    val parser = DeepLinkParser()
    val handler = DeepLinkHandler(parser)
    val intent = handler("myapp:-.product/123456")
    // Launch your Activity

    View Slide

  15. Invoking Instances
    val parser = DeepLinkParser()
    val handler = DeepLinkHandler(parser)
    val intent = handler("myapp://product/123456")
    // Launch your Activity

    View Slide

  16. A tiny web DSL
    sealed class HTTPMethod
    object GET : HTTPMethod()
    object POST : HTTPMethod()
    class Status {
    var code: Int = 0
    var payload: String = ""
    }
    class HttpRequest {
    var method: HTTPMethod = GET
    var body: String = ""
    var query: String = ""
    }
    class HttpResponse {
    var internalMessage: String = ""
    lateinit var status: Status
    }

    View Slide

  17. class Endpoint {
    lateinit var request: HttpRequest
    lateinit var response: HttpResponse
    fun handle() {
    -. TODO
    }
    }

    View Slide

  18. fun endpoint(
    path: String,
    block: Endpoint.() %& Unit) =
    Endpoint().apply { block() }.handle()
    endpoint("/api/product") {
    }

    View Slide

  19. class Endpoint {
    lateinit var request: HttpRequest
    lateinit var response: HttpResponse
    fun request(block: HttpRequest.() %& Unit) {
    request = HttpRequest().apply { block() }
    }
    fun response(block: HttpResponse.() %& Unit) {
    response = HttpResponse().apply { block() }
    }
    fun handle() {
    -. TODO
    }
    }
    endpoint("/api/product") {
    request {
    method = POST,
    body = "{ Some json }"
    }
    response {
    }
    }

    View Slide

  20. endpoint("/api/product") {
    request {
    method = POST,
    body = "{ Some json }"
    }
    response {
    status {
    code = 200
    payload = "{ Some json }"
    }
    }
    }
    class HttpResponse {
    var internalMessage: String = ""
    lateinit var status: Status
    fun status(setup: Status.() %& Unit) {
    status = Status().apply { setup() }
    }
    }

    View Slide

  21. endpoint("/api/product") {
    request {
    method = POST,
    body = "{ Some json }"
    }
    response {
    status {
    code = 200
    payload = "{ Some json }"
    }
    }
    }
    Can we do better ?

    View Slide

  22. class Endpoint {
    lateinit var request: HttpRequest
    lateinit var response: HttpResponse
    fun request(block: HttpRequest.() %& Unit) {
    request = HttpRequest().apply { block() }
    }
    fun response(block: HttpResponse.() %& Unit) {
    response = HttpResponse().apply { block() }
    }
    fun handle() {
    -. TODO
    }
    }

    View Slide

  23. class Endpoint {
    lateinit var request: HttpRequest
    lateinit var response: HttpResponse
    fun request(block: HttpRequest.() %& Unit) {
    request = HttpRequest().apply { block() }
    }
    fun response(block: HttpResponse.() %& Unit) {
    response = HttpResponse().apply { block() }
    }
    fun handle() {
    -. TODO
    }
    }

    View Slide

  24. class Endpoint {
    lateinit var request: HttpRequest
    lateinit var response: HttpResponse
    fun request(block: HttpRequest.() %& Unit) {
    request = HttpRequest().apply { block() }
    }
    fun response(block: HttpResponse.() %& Unit) {
    response = HttpResponse().apply { block() }
    }
    fun handle() {
    -. TODO
    }
    }
    class HttpResponse {
    var internalMessage: String = ""
    lateinit var status: Status
    fun status(setup: Status.() %& Unit) {
    status = Status().apply { setup() }
    }
    }

    View Slide

  25. class Endpoint {
    lateinit var request: HttpRequest
    lateinit var response: HttpResponse
    fun request(block: HttpRequest.() %& Unit) {
    request = HttpRequest().apply { block() }
    }
    fun handle() {
    -. TODO
    }
    }
    class HttpResponse {
    var internalMessage: String = ""
    lateinit var status: Status
    operator fun invoke(
    setup: Status.() -> Unit) {
    status = Status().apply { setup() }
    }
    }

    View Slide

  26. endpoint("/api/product") {
    request {
    method = POST
    body = "{ Some json }"
    }
    response {
    code = 200
    payload = "{ Some json }"
    }
    }
    endpoint("/api/product") {
    request {
    method = POST,
    body = "{ Some json }"
    }
    response {
    status {
    code = 200
    payload = "{ Some json }"
    }
    }
    }
    Before After

    View Slide

  27. http://shop.oreilly.com/product/0636920052999.do

    View Slide

  28. AWESOME
    COMPANIONS

    View Slide

  29. View Slide

  30. interface FormValidator {
    fun validate(name: String?): Boolean
    }
    class FormPresenter {
    private companion object : FormValidator {
    override fun validate(name: String?) =
    name/0isNotEmpty()
    +, throw IllegalArgumentException("Invalid Name")
    }
    }

    View Slide

  31. interface FormValidator {
    fun validate(name: String?): Boolean
    }
    class FormPresenter {
    private companion object : FormValidator {
    override fun validate(name: String?) =
    name/0isNotEmpty()
    +, throw IllegalArgumentException("Invalid Name")
    }
    }

    View Slide

  32. Fake Constructors
    val sampa = Location("23.5505° S", "46.6333° W")
    data class Location(
    val latitude: Float,
    val longitude: Float
    )

    View Slide

  33. data class Location(
    val latitude: Float,
    val longitude: Float) {
    companion object {
    operator fun invoke(lat: String, long: String) =
    Location(parse(lat), parse(long))
    private fun parse(coordinate: String): Float {
    -. convert the String representation
    }
    }
    }

    View Slide

  34. data class Location(
    val latitude: Float,
    val longitude: Float) {
    companion object {
    operator fun invoke(lat: String, long: String) =
    Location(parse(lat), parse(long))
    private fun parse(coordinate: String): Float {
    -. convert the String representation
    }
    }
    }

    View Slide

  35. NOTHING
    ELSE
    MATTERS

    View Slide

  36. The Nothing Type
    https://en.wikipedia.org/wiki/Bottom_type
    " In type theory, a theory within mathematical logic,
    the bottom type is the type that has no values. It is also
    called the zero or empty type, and is sometimes denoted
    with falsum (⊥).
    A function whose return type is bottom cannot return any
    value. In the Curry–Howard correspondence, the bottom
    type corresponds to falsity. "

    View Slide

  37. /!"
    * The type with only one value: the Unit object.
    * This type corresponds to the `void` type
    * in Java.
    #$
    public object Unit {
    override fun toString() = "kotlin.Unit"
    }

    View Slide

  38. Kotlin Types
    Nothing
    SomeType
    Any
    Nothing?
    SomeType?
    Any?

    View Slide

  39. /!"
    * Always throws [NotImplementedError]
    * stating that operation is not implemented.
    #$
    @kotlin.internal.InlineOnly
    public inline fun TODO(): Nothing =
    throw NotImplementedError()

    View Slide

  40. View Slide

  41. Nothing and flow control
    inline fun guard(
    receiver: T?,
    block: () %& Nothing): T {
    if (receiver 12 null) { block() }
    return receiver
    }
    https://gist.github.com/hzsweers/463500043b1a9708ba4e26c7ad1862fd

    View Slide

  42. Nothing and flow control
    inline fun guard(
    receiver: T?,
    block: () -> Nothing): T {
    if (receiver 12 null) { block() }
    return receiver
    }
    https://gist.github.com/hzsweers/463500043b1a9708ba4e26c7ad1862fd

    View Slide

  43. fun guarding() {
    val conference: String? = "Kotlin Summit"
    }

    View Slide

  44. fun guarding() {
    val conference: String? = "Kotlin Summit"
    val guarded = guard(conference) { return }
    }

    View Slide

  45. fun guarding() {
    val conference: String? = "Kotlin Summit"
    val guarded = guard(conference) { return }
    val guardedAgain = guard(conference) {
    throw IllegalStateException("Null konf")
    }
    }

    View Slide

  46. fun guarding() {
    val conference: String? = "Kotlin Summit"
    val guarded = guard(conference) { return }
    val guardedAgain = guard(conference) {
    throw IllegalStateException("Null konf")
    }
    val invalid = guard(conference) {
    -.Compiler error because not
    -.returned or thrown an exception!
    }
    }

    View Slide

  47. Nothing and Generics
    sealed class Tree
    class Node(
    val left: Tree,
    val right: Tree) : Tree()
    class Leaf(val value: T) : Tree()
    object Empty : Tree()

    View Slide

  48. val tree = Node(
    Leaf("1"),
    Node(
    Leaf("2"),
    Empty
    )
    )
    1
    2

    View Slide

  49. sealed class Result
    data class Value(val value: V) :
    Result()
    data class Error(val error: E) :
    Result()

    View Slide

  50. ADVANCING ON
    DELEGATION

    View Slide

  51. interface Validation {
    fun validate(input: String): Boolean
    }
    class NameValidator : Validation {
    override fun validate(input: String) =
    input.isNotEmpty() 34 input.length 56 2
    }
    Class delegation

    View Slide

  52. val accepted = FormViewModel().validate("Bira")
    class FormViewModel : Validation by NameValidator()
    Class delegation

    View Slide

  53. class FormViewModel : FormValidator by Internal {
    companion object Internal : FormValidator {
    override fun validate(name: String?) =
    name/0isNotEmpty()+, false
    }
    }
    val rejected = FormViewModel().validate("Hi")

    View Slide

  54. class FormViewModel : FormValidator by Internal {
    companion object Internal : FormValidator {
    override fun validate(name: String?) =
    name/0isNotEmpty()+, false
    }
    }
    val rejected = FormViewModel().validate("Hi")

    View Slide

  55. interface HandleSuccess {
    fun showResult()
    }
    interface HandleError {
    fun showError()
    }
    interface LoginScreen : HandleSuccess, HandleError
    interface ProductScreen : HandleSuccess, HandleError

    View Slide

  56. interface HandleSuccess {
    fun showResult()
    }
    interface HandleError {
    fun showError()
    }
    interface LoginScreen : HandleSuccess, HandleError
    interface ProductScreen : HandleSuccess, HandleError

    View Slide

  57. Selective delegation
    fun performOperation(behavior: C)
    where C : HandleSuccess, C : HandleError {
    try {
    executeSomething()
    behavior.showResult()
    } catch (error: Throwable) {
    behavior.showError()
    }
    }

    View Slide

  58. Selective delegation
    fun performOperation(behavior: C)
    where C : HandleSuccess, C : HandleError {
    try {
    executeSomething()
    behavior.showResult()
    } catch (error: Throwable) {
    behavior.showError()
    }
    }

    View Slide

  59. PLUS,
    ULTRA !!!

    View Slide

  60. class CheckProfile {
    companion object : (Int) %& Boolean by ShouldBeLegal
    }
    val canDrink = CheckProfile(35)
    object ShouldBeLegal : (Int) %& Boolean {
    override fun invoke(age: Int) = age 5618
    }

    View Slide

  61. What if I told you
    that extensions are
    not only for objects
    ???

    View Slide

  62. val checkNotDead: (Int) %& Boolean = { it < 150 }
    fun ((Int) %& Boolean).report(age: Int) =
    this.invoke(age)
    .also { print((if (it) "Ok" else "Not OK")) }
    fun misjudge(age: Int) = checkNotDead.report(age)

    View Slide

  63. View Slide

  64. Final Remarks
    Kotlin has it's secrets, go explore !!!
    Learn more hidden features for fun and profit

    View Slide

  65. https://speakerdeck.com/ubiratansoares

    View Slide

  66. UBIRATAN
    SOARES
    Computer Scientist by ICMC/USP
    Software Engineer, curious guy
    Google Developer Expert for Android / Kotlin
    Teacher, speaker, etc, etc

    View Slide

  67. THANK YOU
    @ubiratanfsoares
    ubiratansoares.github.io
    https://br.linkedin.com/in/ubiratanfsoares

    View Slide