The articles

Kotlin Jetpack: The Beginning

00. The Kotlin Pit Guide for Java Developers

01. Start with a Demo of worshiping a God

02. What is the Experience of Writing Gradle scripts with Kotlin?

03. Kotlin programming’s Triple Realm

preface

1. How important are higher-order functions?

Higher order functions play an important role in Kotlin. It is the cornerstone of Kotlin functional programming, and is a key element of various frameworks such as coroutines, Jetpack Compose, and Gradle Kotlin DSL. High order function master, will let us read the source code “like a tiger with wings added”.

This article will explain Kotlin higher-order functions, Lambda expressions, and function types in the simplest possible way. At the end of this article, we’ll write an HTML Kotlin DSL ourselves.

preparation

  • Update the Android Studio version to the latest
  • Clone our Demo project locally and open it with Android Studio:

Github.com/chaxiu/Kotl…

  • Switch to branch:chapter_04_lambda
  • I strongly suggest that you follow this article to practice, the actual combat is the essence of this article

The body of the

1. Function types, higher-order functions, Lambda, what are they respectively?

1-1 What is the Function Type?

As the name suggests: a function type is the type of a function.

// (Int, Int) ->Float
/ / write write write
fun add(a: Int, b: Int): Float { return (a+b).toFloat() }
Copy the code

The function type is obtained by abstracting the parameter type and return value type of the function. (Int, Int) -> Float represents two function types with Int return values of type Float.

What is the higher order function 1-2?

Higher-order functions are functions that use functions as arguments or return values.

That’s a little convoluted, so let’s just look at the example. Kotlin is a typical higher-order function if you use Kotlin to listen for click events in Android.

// Function is a higher-order function as an argument
/ / left
fun setOnClickListener(l: (View) - >Unit){... }Copy the code

What is Lambda 1-3?

Lambda can be understood as shorthand for a function.

fun onClick(v: View): Unit{... } setOnClickListener(::onClick)// Replace function references with Lambda expressionssetOnClickListener({v: View -> ... })Copy the code

Seeing this, if you are not confused, then congratulations, this means you have a high understanding, or you have a good foundation; If you feel a little confused, that’s ok. See the explanation below.


2. Why Lambda and higher-order functions?

When I first came into contact with higher-order functions and Lambda, I always had a question: why are Lambda and higher-order functions introduced? There was no answer to this question in the official documentation, so I had to find it myself.

2-1 What problem does Lambda and higher-order functions solve?

Let’s take a look at a practical example. Here’s the View definition in Android. I’ve omitted most of the code:

// View.java
private OnClickListener mOnClickListener;
private OnContextClickListener mOnContextClickListener;

// Listen for finger click events
public void setOnClickListener(OnClickListener l) {
    mOnClickListener = l;
}

// An interface is defined specifically for passing this click event
public interface OnClickListener {
    void onClick(View v);
}

// Listen for mouse click events
public void setOnContextClickListener(OnContextClickListener l) {
    getListenerInfo().mOnContextClickListener = l;
}

// An interface is defined specifically for passing this mouse click event
public interface OnContextClickListener {
    boolean onContextClick(View v);
}
Copy the code

Set the click event and mouse click event in Android like this:

// Set the finger click event
image.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) { gotoPreview(); }});// Set the mouse click event
image.setOnContextClickListener(new View.OnContextClickListener() {
    @Override
    public void onContextClick(View v) { gotoPreview(); }});Copy the code

Do you find this kind of code very wordy?

Now that we’re pretending to be language designers, let’s look at some of the problems with the code above:

  • Definer: For every method added, an interface is added:OnClickListener.OnContextClickListener
  • Caller: Needs to write a bunch of anonymous inner classes that are verbose, tedious, and unfocused

If you look closely at the code above, the developer really only cares about one line of code:

gotoPreview();
Copy the code

If the core logic is pulled out, it’s most concise:

image.setOnClickListener { gotoPreview() }
image.setOnContextClickListener { gotoPreview() }
Copy the code

What did the designers of Kotlin do? Is this:

  • Replace interface definitions with function types
  • Use Lambda expressions as function arguments

The Kotlin equivalent to view.java above is as follows:

//View.kt
var mOnClickListener: ((View) -> Unit)? = null
var mOnContextClickListener: ((View) -> Unit)? = null

fun setOnClickListener(l: (View) - >Unit) {
    mOnClickListener = l;
}

fun setOnContextClickListener(l: (View) - >Unit) {
    mOnContextClickListener = l;
}
Copy the code

The above approach has the following benefits:

  • Definer: Reduced the definition of two interface classes
  • Caller: More concise code

Android does not provide a Kotlin implementation of View. Java. Why can we use lambdas in Demo to simplify event listening?

// In real development, we often use this simplification
setOnClickListener { gotoPreview() }
Copy the code

The reason is this: Since OnClickListener meets the SAM transformation requirements, the compiler automatically does a layer of transformation for us that allows us to simplify our function calls with Lambda expressions.

So, what the hell is SAM?

2-2 Single Abstract Method Conversions

SAM(Single Abstract Method), as its name implies, is a class or interface that has only one Abstract Method, but in Kotlin and Java8, SAM stands for an interface that has only one Abstract Method. If an interface meets SAM’s requirements, the compiler can perform SAM conversions that allow us to use Lambda expressions to abbreviate the parameters of an interface class.

Note: SAM in Java8 has an explicit name: FunctionalInterface.

The FunctionalInterface has the following limitations:

  • It must be an interface, not an abstract class
  • The interface has one and only one abstract method, the number of abstract methods must be 1, and the default implementation can have multiple methods.

That is, for View. Java, it is Java code, but the Kotlin compiler knows that its argument OnClickListener is eligible for SAM conversion, so it automatically does the following conversion:

Before conversion:

public void setOnClickListener(OnClickListener l)
Copy the code

After the transformation:

fun setOnClickListener(l: (View) - >Unit)
// It works like this:
fun setOnClickListener(l: ((View!). ->Unit)?
Copy the code

((View!) -> Unit)? Represents, this parameter may be null.

2-3 Eight ways to write Lambda expressions raised

When Lambda expressions are used as function arguments, there are cases where it is possible to abbreviate them to make our code look cleaner. However, this is a headache for most beginners, and it can be confusing to write the same code in 8 different ways.

To understand the shorthand logic of Lambda expressions, it’s simple: write more.

Friends can follow my next process to write together:

2-3-1, number one

This is the original code, which essentially defines an anonymous inner class with the object keyword:

image.setOnClickListener(object: View.OnClickListener {
    override fun onClick(v: View?). {
        gotoPreview(v)
    }
})
Copy the code
2-3-2, second way

If we delete the object keyword, it becomes a Lambda expression, so the override method in it should also be deleted:

image.setOnClickListener(View.OnClickListener { view: View? ->
    gotoPreview(view)
})
Copy the code

The view. OnClickListener above is called: SAM Constructor — SAM Constructor, which the compiler generates for us. Kotlin allows us to define Lambda expressions in this way.

Consider:

At this time,View.OnClickListener {}Semantically Lambda expressions, but syntactically Lambda expressionsAnonymous inner class. Is that true?

So 2-3-3 number three

Since Kotlin’s Lambda expression does not need SAM Constructor, it can also be deleted.

image.setOnClickListener({ view: View? ->
    gotoPreview(view)
})
Copy the code
Two, three, four

Since Kotlin supports type derivation, View? Can be deleted:

image.setOnClickListener({ view ->
    gotoPreview(view)
})
Copy the code
The 2-3-5 fifth way

When a Kotlin Lambda expression has only one argument, it can be written as it.

image.setOnClickListener({ it ->
    gotoPreview(it)
})
Copy the code
The sixth way to write

The it of Kotlin Lambda can be omitted:

image.setOnClickListener({
    gotoPreview(it)
})
Copy the code
2-3-7 number seven

When a Kotlin Lambda is the last argument to a function, the Lambda can be moved outside:

image.setOnClickListener() {
    gotoPreview(it)
}
Copy the code
So 2-3-8, number eight

When Kotlin only has a Lambda as a function argument, () can be omitted:

image.setOnClickListener {
    gotoPreview(it)
}
Copy the code

Follow this process, write in the IDE a few times, and you will understand. Be sure to write. You can’t remember from reading.

2-4 The relationship between function types, higher-order functions, and Lambda expressions

  • The function of theThe parameter typesandReturn value typeAnd when I abstract it out, I getFunction types.(View) -> UnitRepresents theThe parameter typesIs the ViewReturn value typeIs the function type of Unit.
  • If the argument to a functionorIf the type of the return value is a function type, the function isHigher-order functions. Obviously, we’ve just written a higher-order function, but it’s simpler.
  • Lambda is one type of functionshorthand

The relation between function types, higher-order functions and Lambda expressions:

Go back to the official documentation for an example:

fun <T, R> Collection<T>.fold(
    initial: R, 
    combine: (acc: R.nextElement: T) - >R
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}
Copy the code

Function type: (acc: R, nextElement: T) -> R This function takes two arguments, the first of type R, the second of type T, and the return type of the function is R.


A.(B, C) -> D

To be honest, the name isn’t very friendly to beginners either: Function Types With Receiver. I know every word of it, but With this little information, it’s hard for beginners to understand its nature.

The question remains: Why?

3-1 Why introduce: function types with receivers?

As we mentioned in the previous chapter, we used apply to simplify logic like this:

Modify before:

if(user ! =null) {... username.text = user.name website.text = user.blog image.setOnClickListener { gotoImagePreviewActivity(user) } }Copy the code

Revised:

user? .apply { ... username.text = name website.text = blog image.setOnClickListener { gotoImagePreviewActivity(this)}}Copy the code

How should the apply method be implemented?

This is a simplified Lambda expression. Let’s work backwards to see what it looked like before it was simplified:

// Apply must be a function, so there is (), but it is omitteduser? .apply() { ... }// Lambda must be inside ()user? .apply({ ... })// Since the this in gotoImagePreviewActivity(this) represents the user
// So user should be an argument to apply with the name: thisuser? .apply({this: User -> ... })
Copy the code

So, now that the problem is clear, apply actually accepts a Lambda expression: {this: User ->… }. Let’s try implementing the apply method:

fun User.apply(block: (self: User) - >Unit): User{
    block(self)
    return this} user? .apply { self: User -> ... username.text = self.name website.text = self.blog image.setOnClickListener { gotoImagePreviewActivity(this)}}Copy the code

Since function parameters in Kotlin are not allowed to be named this, the self I use here, our self-written apply, still accesses member variables through self.name, but Kotlin’s language designers can do this:

/ / to this
/ / left
fun User.apply(block: (this: User) - >Unit): User{
// We need to pass parameters here
/ / left
    block(this)
    return this} user? .apply {this: User ->
    ...
// This can be omitted
/ / left
    username.text = this.name
    website.text = blog
    image.setOnClickListener { gotoImagePreviewActivity(this)}}Copy the code

As you can see from the above example, our backward apply implementation is cumbersome and requires us to call: block(this) ourselves, so Kotlin introduced a function type with receiver to simplify the definition of apply:

// Function type with receiver
/ / left
fun User.apply(block: User. () - >Unit): User{
// Do not pass this again
/ / left
    block()
    return this} user? .apply {this: User ->
    ...
    username.text = this.name
    website.text = this.blog
    image.setOnClickListener { gotoImagePreviewActivity(this)}}Copy the code

Now, here’s the key. Does the apply method look like adding a member method apply() to the User?

class User() {
    val name: String = ""
    val blog: String = ""
    
    fun apply(a) {
        // Member methods can access member variables through this
        username.text = this.name
        website.text = this.blog
        image.setOnClickListener { gotoImagePreviewActivity(this)}}}Copy the code

So function types with receivers are, on the surface, equivalent to member methods. But essentially, it’s still done by the compiler injecting this.

A table to summarize:

There is 2:

Can function types with receivers also represent extension functions?

There is 3:

Excuse me:A.(B, C) -> DWhat kind of function does it represent?

HTML Kotlin DSL practice

The official documentation mentions this in the section on higher-order functions: Using higher-order functions to implement type-safe HTML builders. The official documentation example is a bit more complex, so let’s write a simplified version of the tutorial.

4-1 Effect display:

val htmlContent = html {
    head {
        title { "Kotlin Jetpack In Action" }
    }
    body {
        h1 { "Kotlin Jetpack In Action"}
        p { "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -" }
        p { "A super-simple project demonstrating how to use Kotlin and Jetpack step by step." }
        p { "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -" }
        p { "I made this project as simple as possible," +
                " so that we can focus on how to use Kotlin and Jetpack" +
                " rather than understanding business logic." }
        p {"We will rewrite it from \"Java + MVC\" to" +
                " \"Kotlin + Coroutines + Jetpack + Clean MVVM\"," +
                " line by line, commit by commit."}
        p { "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -" }
        p { "ScreenShot:" }
        img(src = "https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/15/172b55ce7bf25419~tplv-t2oaga2asx-image.image",
         alt = "Kotlin Jetpack In Action")
    }
}.toString()

println(htmlContent)
Copy the code

The output looks like this:

<html>
  <head>
    <title>
      Kotlin Jetpack In Action
    </title>
  </head>
  <body>
    <h1>
      Kotlin Jetpack In Action
    </h1>
    <p>
      -----------------------------------------
    </p>
    <p>
      A super-simple project demonstrating how to use Kotlin and Jetpack step by step.
    </p>
    <p>
      -----------------------------------------
    </p>
    <p>
      I made this project as simple as possible, so that we can focus on how to use Kotlin and Jetpack rather than understanding business logic.
    </p>
    <p>
      We will rewrite it from "Java + MVC" to "Kotlin + Coroutines + Jetpack + Clean MVVM", line by line, commit by commit.
    </p>
    <p>
      -----------------------------------------
    </p>
    <p>
      ScreenShot:
    </p>
    <img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/15/172b55ce7bf25419~tplv-t2oaga2asx-image.image" alt="Kotlin Jetpack In Action" /img>
  </body>
</html>
Copy the code

4-2 HTML Kotlin DSL implementation

4-2-1 Defines interfaces of node elements
interface Element {
    // Each node needs to implement the render method
    fun render(builder: StringBuilder, indent: String): String
}
Copy the code

Kotlin Jetpack In Action All HTML nodes implement the Element interface and implement HTML code splice In the render method:

4-2-2 defines the base classes
/** * Each node has a name, content:  Kotlin Jetpack In Action  */
open class BaseElement(val name: String, val content: String = "") : Element {
    // Each node has many children
    val children = ArrayList<Element>()
    
    val hashMap = HashMap<String, String>()
    
     Kotlin Jetpack In Action  */
    override fun render(builder: StringBuilder, indent: String): String {
        builder.append("$indent<$name>\n")
        if (content.isNotBlank()) {
            builder.append("  $indent$content\n")
        }
        children.forEach {
            it.render(builder, "$indent  ")
        }
        builder.append("$indent</$name>\n")
        return builder.toString()
    }
}
Copy the code
4-2-3 Defines each child node:
// This is the outermost HTML tag: < HTML >
class HTML : BaseElement("html") {
    fun head(block: Head. () - >Unit): Head {
        val head = Head()
        head.block()
        this.children += head
        return head
    }

    fun body(block: Body. () - >Unit): Body {
        val body = Body()
        body.block()
        this.children += body
        return body
    }
}

// This is followed by the  tag
class Head : BaseElement("head") {
    fun title(block: () -> String): Title {
        val content = block()
        val title = Title(content)
        this.children += title
        return title
    }
}

// This is the title tag inside the Head.
class Title(content: String) : BaseElement("title", content)

// Then the  tag
class Body : BaseElement("body") {
    fun h1(block: () -> String): H1 {
        val content = block()
        val h1 = H1(content)
        this.children += h1
        return h1
    }

    fun p(block: () -> String): P {
        val content = block()
        val p = P(content)
        this.children += p
        return p
    }

    fun img(src: String, alt: String): IMG {
        val img = IMG().apply {
            this.src = src
            this.alt = alt
        }

        this.children += img
        return img
    }
}

// The rest of the tags are inside the body
class P(content: String) : BaseElement("p", content)
class H1(content: String) : BaseElement("h1", content)

class IMG : BaseElement("img") {
    var src: String
        get() = hashMap["src"]!!!!!set(value) {
            hashMap["src"] = value
        }

    var alt: String
        get() = hashMap["alt"]!!!!!set(value) {
            hashMap["alt"] = value
        }
    
    // Splice  tags
    override fun render(builder: StringBuilder, indent: String): String {
        builder.append("$indent<$name")
        builder.append(renderAttributes())
        builder.append("/$name>\n")
        return builder.toString()
    }

    private fun renderAttributes(a): String {
        val builder = StringBuilder()
        for ((attr, value) in hashMap) {
            builder.append(" $attr= \"$value\ "")}return builder.toString()
    }
}

Copy the code
4-2-4 Defines the methods for outputting HTML code
fun html(block: HTML. () - >Unit): HTML {
    val html = HTML()
    html.block()
    return html
}
Copy the code

4-3 DISPLAY of HTML code

class WebActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_web)

        val myWebView: WebView = findViewById(R.id.webview)
        myWebView.loadDataWithBaseURL(null, getHtmlStr(), "text/html"."UTF-8".null);
    }

    private fun getHtmlStr(a): String {
        return html {
            head {
                title { "Kotlin Jetpack In Action" }
            }
            body {
                h1 { "Kotlin Jetpack In Action"}
                p { "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -" }
                p { "A super-simple project demonstrating how to use Kotlin and Jetpack step by step." }
                p { "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -" }
                p { "I made this project as simple as possible," +
                        " so that we can focus on how to use Kotlin and Jetpack" +
                        " rather than understanding business logic." }
                p {"We will rewrite it from \"Java + MVC\" to" +
                        " \"Kotlin + Coroutines + Jetpack + Clean MVVM\"," +
                        " line by line, commit by commit."}
                p { "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -" }
                p { "ScreenShot:" }
                img(src = "https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/15/172b55ce7bf25419~tplv-t2oaga2asx-image.image", alt = "Kotlin Jetpack In Action")
            }
        }.toString()
    }
}
Copy the code

4-4 Display effect:

See my GitHub Commit for details on the above changes.

4-5 Advantages of HTML DSL

  • Type safety
  • Automatic completion is supported
  • Support error message
  • Save code and improve development efficiency

4-6 summary

  • The above DSL code simply look is very difficult to understand, must download down step by step debugging
  • This case is full of higher-order functions, and it’s helpful to understand higher-order functions
  • This case is not complete, there are many HTML features are not implemented, interested partners can improve try
  • In this case, I’m trying to refrain from using other Kotlin features, such as generics. If you use generics, your code will be much cleaner. If you’re interested, try it out

5 concludes

  • This article doesn’t cover everything about higher-order functions, but it covers the most important and difficult parts. It will be easier for you to read the official document after reading this article
  • To repeat: no amount of tutorials is worth a few lines of code

Now that you’ve seen it, give it a thumbs up!

Stay tuned for the next chapter: Kotlin generics

Directory – >Kotlin Jetpack In Action