preface

I’ve already written a post called “What’s it Like to Work on An Android Project with Kotlin?” In the article. Most of what is mentioned in this article is about the Kotlin language itself, but SOME of Kotlin’s special support for Android is not included in this article. Some of my friends have already given me suggestions. So this time we’ll probably go into more detail about what else Kotlin enjoys about developing Android.

The body of the

1. Say NO to findViewById

Unlike In JAVA, findViewById itself is much simpler in Kotlin, thanks to Kotlin’s type inference and post-transition syntax:

val onlyTv = findViewById(R.id.onlyTv) as TextView
Copy the code

It’s very simple, but if it’s just like this, surely everyone will gush to death me: with such a point gap also take out to do things?

Of course not. With the support of the official Kuanko, this has changed a lot. For example,

val onlyTv = find<TextView>(R.id.onlyTv)
val onlyTv: TextView = find(R.id.onlyTv)
Copy the code

The question is: what the hell is find? Let’s go over to find’s source code:

inline fun <reified T : View> Activity.find(id: Int): T = findViewById(id) as T
Copy the code

FindViewById: findViewById: findViewById: findViewById: findViewById: findViewById: findViewById: findViewById: findViewById

Many of Kotlin’s other amazing implementations are based on a combination of basic features, such as the find method above, which I combined with the lazy proxy provided natively:

class MainActivity : AppCompatActivity() {

    val onlyTv by lazy { find<TextView>(R.id.onlyTv) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        onlyTv.text = "test"}}Copy the code

Although the above code is the author of a temporary whimsical play, but no problem after testing. In other words, I can put the view declaration along with findViewById in the declaration like this.

And that’s just with lazy proxies provided natively, which we could have done if we wanted:

val onlyTv by myOwnDelegate<TextView>(R.id.onlyTv)
Copy the code

What if we give myOwnDelegate a name?

val onlyTv by find<TextView>(R.id.onlyTv)
val onlyTv by findView<TextView>(R.id.onlyTv)
val onlyTv by findViewById<TextView>(R.id.onlyTv)
Copy the code

Pretty good, right? What else should I rely on (zi) lai (xing) note (che) enter?

Sometimes, it really depends on how big our imagination is. Just as you thought that was all I wanted to say (I wrote it myself and thought it was the end of the section) would you believe me if I told you that you didn’t have to write a single word of code?

Here’s a screenshot for proof:

There is no trace of onlyTv declaration and it is impossible to inherit from AppCompatActivity. And when you try to command/ CTRL + left-click on onlyTv to see where onlyTv comes from, you’ll find yourself jumping into activity_main’s layout file:

As sharp-eyed friends may have discovered, the only truth is:

import kotlinx.android.synthetic.main.activity_main.*
Copy the code

Please forgive my limited ability to explain the reason for the moment. To be sure, with Anko’s help, you only need to write an import code based on the id of the layout, and then you can use the ID of the layout as the name of the View object. Not only can you do this in an activity, you can even do this in viewA.viewb. viewC, so you don’t have to worry about what to write in adapter.

There’s no findViewById, so there’s no null pointer; Without cast, there are almost no cast exceptions.

PS. What if I told you that The Databinding library already relies on Kotlin?

2. StartActivity is simple and rude

We used to do Activity jumps like this:

Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
Copy the code

For startActivity, I have to create a new Intent, especially if I want to pass a parameter:

Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.putExtra("name"."Zhang");
intent.putExtra("age"27); startActivity(intent);Copy the code

I don’t know if you are tired of love?

With the help of Anko, startActivity looks like this:

startActivity<MainActivity>()
startActivity<MainActivity>("name" to "Zhang"."age" to 27)
startActivityForResult<MainActivity>(101, "name" to "Zhang"."age" to 27)
Copy the code

With no arguments, you simply call startActivity with an Activity Class generic to tell you where to go. If so, this method allows you to pass vararg params: Pair
,>

Does the code feel much smoother to write and read?

3. Small toast

A toast in JAVA would look something like this:

Toast.makeText(context, "this is a toast", Toast.LENGTH_SHORT).show();
Copy the code

The above code is purely by hand, if there is any error please correct. I have to say it’s smelly and long, although there are a lot of considerations involved, it’s just so inconvenient to use, and it’s easy to forget the last show(). I bet there’s not a single Android developer over a year old who hasn’t wrapped a ToastUtil.

Encapsulation would look something like this:

ToastUtil.showShort(context, "this is a toast");
Copy the code

If you deal with the context problem, you can shorten it like this:

ToastUtil.showShort("this is a toast");
Copy the code

It’s a little bit minimalist, isn’t it?

Well, it’s time to see how Anko does it:

context.toast("this is a toast")
Copy the code

If you are already in a context (such as an activity) :

toast("this is a toast")
Copy the code

If you want a long toast:

longToast("this is a toast")
Copy the code

It extends the toast and longToast methods of the Context class to see what’s going on. It’s just much cleaner and more intuitive than any tool class.

4. Use the Apply method to combine data

Suppose there are three classes A, B, and C:

class A(val b: B)

class B(val c: C)

class C(val content: String)
Copy the code

You can see that A has B, and B has C. In actual development, we sometimes encounter data that is more complex than this, with a deep level of nesting. At this point, initializing class A data in JAVA can become A pain in the neck. Such as:

C c = new C("content");
B b = new B(c);
A a = new A(b);
Copy the code

Again, if the relationship between A, B, and C is very simple, if we have A lot of data to combine, then we need to initialize A lot of objects for assignment, modification, etc. If I’m not clear enough, think about what it feels like to have a layout in JAVA code.

Of course, there are solutions in JAVA, such as dialogs, commonly used in Android, which use Builder mode for configuration. (Having said that, the Builder pattern is basically a DSL for the JAVA language.)

But in more complex cases, even with the help of design patterns, it’s hard to keep the code readable. So does Kotlin have any good tips or tricks for solving this problem?

Kotlin has a method called apply that looks like this:

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
Copy the code

For those of you without Kotlin’s foundation, this is going to be a little confusing. Let’s ignore some of the details, extract the key information, and reformat it:

public fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}
Copy the code

1. We can see that T is a generic type, and there is no constraint on T, so T can be read as: I am extending a method named “apply” to all classes;

2. The: T at the end of the first line indicates that I am finally returning a T class. And we can also see inside the method that the last return this tells us that I’m actually going to return the object that called the method;

3. Before returning this, I execute a block(), which means that the block itself must be a method. As you can see, the block argument received by apply is of a slightly special type. It is not a String or any other explicit type, but T.() -> Unit;

4.T.() -> Unit is a method that returns a Unit object in context. Since Unit is identical to Void in JAVA, it can be understood that the return value is not required. The meaning of the block here is clear: a method that executes on T, the object that calls Apply, and does not return a value.

With that in mind, let’s look at this code:

val textView = TextView(context).apply {
    text = "This is text content."
    textSize = 16f
}
Copy the code

This code initializes a TextView and modifies its text and font size before assigning it to the TextView.

You might think that this is not much of an advantage over JAVA. Don’t worry, let’s take it slow:

layout.addView(TextView(context).apply {
    text = "This is text content."
    textSize = 16f
})
Copy the code

So what? I don’t need to declare a variable or constant to hold the object in order to make changes.

The above A, B, C problems can be implemented with Kotlin as follows:

val a = A().apply {
    b = B().apply {
        c = C("content")}}Copy the code

I just declared an A object, initialized an A, assigned a value to B in the initialized object, and then submitted it to A. The same is true of C in B. I also keep my readability when combinations get complicated:

val a = A().apply {
    b = B().apply {
        c = C("content")
    }

    d = D().apply {
        b = B().apply {
            c = C("test")
        }

        e = E("test")}}Copy the code

What would the above code look like in JAVA? I get dizzy just thinking about it anyway. In the end, this trick is just a combination of (1) extension methods and (2) higher-order functions.

5. Do things with higher-order functions

Look at the code

inline fun debug(code: () -> Unit) {
    if(BuildConfig.DEBUG) { code() } } ... Debug {Timber. Plant (Timber.DebugTree())}Copy the code

The above code starts by defining a global method named debug that takes a method as an argument named code. Then, inside the method body, I determine if the current version is DEBUG, and if so, I call the code method passed in.

Then in our Application, the Debug method becomes the key for executing code based on conditions. I only initialize Timber when I DEBUG the log library.

If that’s not enough, take a look at the following:

supportsLollipop {
    window.statusBarColor = Color.TRANSPARENT
    window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
}
Copy the code

Do the immersive status bar only when the system version is on Lollipop. System apis often have version limits, as opposed to a supportsLollipop keyword, which I’m sure not everyone wants to write every time:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // do something
}
Copy the code

There are many more scenarios like this and you can create your own keywords/code blocks. Such as:

inline fun handleException(code : () -> Unit) {
    try {
        code()
    } catch (e : Exception) {
        e.printStackTrace()
    }
}
...
handleException {
     println(Integer.parseInt("This is clearly not a number."))}Copy the code

Most people can make do with if(xxxxutil.isxxxx ()), but why bother when there is a better solution?

6. Replace utility classes with extension methods

There was a time when I used to write a utility class for string judgment, and it was filled with all kinds of methods. In Kotlin, you can use extension methods instead. Here is part of the String extension method in my project:

fun String.isName(): Boolean {
    if (isEmpty() || length > 10 || contains("")) {
        return false
    }

    val reg = Regex("^[a-zA-Z0-9\u4e00-\u9fa5]+$")
    return reg.matches(this)
}

fun String.isPassword(): Boolean {
    return length in6.. 12 } fun String.isNumber(): Boolean { val regEx ="^ -? [0-9] + $"
    val pat = Pattern.compile(regEx)
    val mat = pat.matcher(this)

    return mat.find()
}
...

println("Zhang".isName())
println("123abc".isPassword())
println("123456".isNumber())
Copy the code
Automatic getters and setters make code more concise

Taking TextView as an example, the codes for obtaining text and setting text in JAVA code are as follows:

String text = textView.getText().toString();
textView.setText("new text");
Copy the code

Here’s what Kotlin says:

val text = textView.text
textView.text = "new text"
Copy the code

If TextView were a native Kotlin class, there would be no getText and setText methods, but a text property. Although the TextView here is a JAVA class, there are both getText and setText methods in the source code, and Kotlin does a similar mapping. When the text attribute is to the right of the equals sign, it is extracting the text attribute (here mapped to getText); When you’re on the left side of the equals sign, you’re doing a setText.

This reminds me of the Preference proxy mentioned in the previous article, which is also related to the fact that when a property is on the left and right of the equals sign, it may trigger something else in Kotlin, rather than an assignment in JAVA.

To be continued…

Supplement:

Looking through the previous project, I found the following code for comparison:

Build and display the BottomSheet
Bottomsheet. Builder(this@ShareActivity, r.style.sharesheetstyle). Sheet (999, r.ravenable.share_circle, R.string.wXSceneTimeline) .sheet(998, R.drawable.share_freind, R.string.wXSceneSession) .listener { _, id -> shareTo(bitmap, target = when(id) { 999 -> SendMessageToWX.Req.WXSceneTimeline 998 -> SendMessageToWX.Req.WXSceneSessionelse -> throw Exception("it can not happen")
            })
        }
        .build()
        .show()
Copy the code
ShowBottomSheet {style = r.style. ShareSheetStyle sheet {icon = r.drawable. Share_circle text = R.string.wXSceneTimeline selected { shareTo(bitmap, SendMessageToWX.Req.WXSceneTimeline) } } sheet { icon = R.drawable.share_freind text = R.string.wXSceneSession selected { shareTo(bitmap, SendMessageToWX.Req.WXSceneTimeline) } } }Copy the code
Apply build data instance
Val obj = WXImageObject(bitmap) val thumb =...... bitmap.recycle() val msg = WXMediaMessage() msg.mediaObject = obj msg.thumbData = thumb val req = SendMessageToWX.Req() req.transaction ="share"
req.scene = target
req.message = msg

WxObject.api.sendReq(req)

Copy the code
DSL version wxObject.api.sendreq (sendMessageTowx.req ().apply {transaction ="share"
            scene = target
            message = WXMediaMessage().apply {
                mediaObject = WXImageObject(bitmap)
                thumbData = ......
                bitmap.recycle()
            }
        }
)
Copy the code

Anyone e who can look at the normal version and see the structural relationships within 3 seconds must be a genius.