【翻譯】ANDROID KTX – 使用Kotlin進行Android開發

原文地址:ANDROID KTX – ANDROID DEVELOPMENT WITH KOTLIN android

[正在翻譯中]web

介紹

Android KTX is an open source library or set of functionalities designed to make the Android development with Kotlin even more pleasant. You can find its website here. The abbreviation KTX stands for Kotlin Extensions, so this library is basically a set of extension functions, extension properties and other top-level functions. In this article, we take a look at what’s inside this library and how we can take advantage of it. This library’s goal is not to add new features to the existing Android APIs, but rather make those APIs easier to use by leveraging the features of the Kotlin language.app

Android KTX 是一個開源的庫,或者說是一個開源的方法集。它的設計目的主要是爲了讓開發者在開發Android應用的時候更愉快。這是它的官方網站。「KTX」是「Kotlin Extensions",所以這個庫本質上是一個擴展方法集,屬性集和頂級函數集。在這篇文章裏,我將帶領你們看一下在這個庫裏究竟有什麼,咱們將如何利用這庫來提升咱們的開發效率。這個庫的目標不是爲了爲已有的Andorid API增長新的特性,而是爲了幫助咱們經過使用kotlin語言來使咱們在開發Android應用的時候更簡潔。ide

Structure of Android KTX A very important thing to note at the beginning is that Android KTX provides functionalities which would be added to many individual projects by the developers most of the time anyway. Arguably, there are also many things that will get included by adding the dependency, which are not going to be used. Thanks to the ProGuard, all the unused code will get stripped out, so there should be no worry regarding the library footprint. Now, let’s take a look at some of the very important and crucial concepts of the Kotlin language which are used for building the Android KTX.函數

Android KTX 結構

首先咱們須要注意一個很是重要的事情:Android KTX 提供了一系列的功能,這些功能會被開發者完整的添加到各個不一樣的項目中去。顯然,有的項目只用到了Android KTX中的一部分功能,但卻不得不把整個庫添加進來。好在有ProGuard,它能夠把全部沒用到的代碼所有清除掉。因此咱們不必爲這個事擔憂。如今,讓咱們一塊兒看下幾個Kotlin 語言中很是重要的概念,正是因爲Kotlin語言的這些特性,才構建出了Android KTX的庫。oop

Extension functions An extension function is one of the Kotlin features that allows us to add a functionality to an existing class without modifying it directly. By adding extension functions to a class, they are represented as simple static methods on bytecode level. Then, we can call those functions on the objects of that class type, just as they were part of the class’s API initially. Here is an example to give a little better insight into the way it’s done. Say, we want to add an extension function to the String class to print itself. Here is how we can do it:網站

擴展函數

擴展函數是Kotlin的一個特性,他可讓咱們不改變現有類的代碼的狀況,給現有類添加新的方法。當咱們給一個現有類添加方法的時候,該方法在字節碼層級是以靜態方法的形式存在的。因此咱們能夠直接在這個類new出來的對象上調用這個方法,就像這個類本省就有這個方法同樣。經過下面這個例子,咱們來看下它具體是怎麼實現的。例如,咱們想要給String類添加一個方法,讓String能夠把本身打印出來。咱們能夠這樣作:ui

fun String.printSelf() {
    println(this)
}
複製代碼

Inside the extension function, we can use this to refer to the current object on which the function is being executed. Then, this function becomes available to be called on any object of String type. So we can do:this

在這個擴展方法內部,咱們能夠直接經過this引用調用這個方法的對象。這樣,咱們就能夠在任何一個String類的對象上調用這個方法。以下:url

fun usage() {
    "Kotlin Rocks".printSelf()
}
複製代碼

Extension properties Similarly to extension function, Kotlin supports extension properties. Also, the way we define them is quite the same:

擴展屬性

和擴展方法相似,Kotlin支持擴展屬性。咱們能夠相同的方式來定義擴展屬性:

val String.isLongEnough: Boolean
    get() = this.length > 5
複製代碼

Then, we can use this extension property on any object of String type:

而後咱們能夠在任何String類對象上使用這個擴展屬性:

"Kotlin".isLongEnough
複製代碼

The behavior of the extension property can only be defined by explicitly providing getter (plus setter for vars). Initializing those properties the ordinary way doesn’t work. The reason behind this is that an extension property does not insert members into the type, so there is no efficient way for it to have a backing field.

擴展屬性這個特性只能經過顯示的提供getter的方式來實現(變量還須要提供setter)。咱們不能經過指定初始值的方式對擴展屬性進行初始化。由於擴展屬性並無真的爲對應的類添加一個成員變量,全部沒一個成員變量來存儲初始化的值。

Top level functions In Kotlin, a function is a first-class citizen. We are allowed to define functions in Kotlin files (.kt) which could afterward be accessed and used from other Kotlin files. This is a very powerful concept. If we define a file inside a package com.example and define functions in it, they can be used simply by importing them into the usage side. Here is an example:

頂級函數

在Kotlin中,函數是第一公民。咱們能夠在Kotlin文件(.kt)中定義方法,而後在其餘的Kotlin文件中訪問或調用它。這個一個很是強大的特性。若是咱們在包com.example建立一個.kt文件,而後在這個文件中定義一個方法,咱們就能夠經過簡單的導包的方式來使用它。例如:

package com.example

fun sum(x: Int, y: Int): Int = x + y
複製代碼

如今我沒就能夠經過簡單的導入在任何.kt文件中使用它:

import com.example.sum

fun test() {
    println(sum(1, 2))
}
複製代碼

An important note here is the access modifier. In the example above, the function sum does not have defined an access modifier, and in Kotlin it’s public by default. Being public, it makes the sum function accessible from any other Kotlin file. Kotlin also has an internal access modifier keyword, which would make this function accessible only in the module where this file exists (a module could contain many different packages). Ultimately, the function could also be private which will make it accessible only from inside the file where it is defined.

這裏,有很重要的一點你須要注意,那就是訪問修飾符。在上面那個例子中,sum方法在定義的時候並無指定訪問修飾符,在Kotlin中,它默認就是public的。做爲一個public的方法,sum能夠在任何.kt文件中訪問。kotlin還有一個訪問修飾符"internal",被它修飾的方法只能在同一module中的其餘.kt文件訪問(一個module可能包含多個package)。最後,還有一個修飾符"private"。被"private"修飾的方法只能被同一個.kt文件下的其餘地方被訪問。

Default arguments Most probably, you are familiar with the overloading concept like in Java already. Overloading is a concept that allows us to define constructors or methods with the same signature, only differing in their parameter list (both types and number of parameters could be different). In Kotlin, this concept is taken a step further, so we could achieve the same result by defining a single function, by specifying default values to some or all of the arguments. Eventually, it boils down to the approach used in Java again. Let’s take a look at the following example:

fun greet(firstName: String, lastName: String = "") {
    println("Hello $firstName $lastName")
}
複製代碼

In this example, the function takes two arguments, but the lastName is optional because by default its value is going to be an empty String. So, when calling this function we are only required to supply a firstName, and we can call it this way:

greet("John")
複製代碼

Kotlin also supports named arguments, so we could call a function supplying the arguments by their names:

greet("John", lastName = "Doe")
複製代碼

This is particularly useful when we have a function with multiple optional arguments and we want to call it with supplying only specific ones, or in a different order.

You can read more about Kotlin’s awesome features in this article.

Deep Dive into Android KTX Now, as we know what the Android KTX library is based on, let’s dig and observe some of the most common extension functions.

Converting URL to URI To begin with, there is a very simple extension function on the Uri class. Many times in Android, we need to convert a String URL into a Uri object, for instance when creating an Intent with data etc. The way we are usually doing this is by calling Uri.parse("string_url"). Android KTX defines an extension function that does the job. Here is how it looks like:

inline fun String.toUri(): Uri = Uri.parse(this)
複製代碼

so we can use it by calling toUri() on any string, like this:

"any_sting_url".toUri()
複製代碼

Editing shared preferences Next, let’s take a look at an extension function defined on the SharedPreferences interface. The usual way of putting values into the SharedPreferences in Android is to call edit() in order to obtain the SharedPreferences.Editor instance. Then, we can insert the values by calling editor.putType("key", typeValue). After that, it is very important to call apply() or commit() on the editor, in order for the values to be stored in the SharedPreferences. Many times we forget doing so, and we waste some time debugging until we notice what is happening. A usual example of storing values in the SharedPreferences looks like this:

val editor = sharedPreferences.edit()
editor.putString("key", value)
editor.apply()
複製代碼

By using the Android KTX extension on the SharedPreferences the code shortens and simplifies quite a lot, and it becomes:

sharedPreferences.edit {
    putString("key", value)
}
複製代碼

The relevant extension function looks like this:

inline fun SharedPreferences.edit( commit: Boolean = false, action: SharedPreferences.Editor.() -> Unit
) {
    val editor = edit()
    action(editor)
    if (commit) {
        editor.commit()
    } else {
        editor.apply()
    }
}
複製代碼

The first parameter to this function is a Boolean value that controls the call to the editor, whether it would use the commit() or the apply() call. Clearly, by default this value is set to false which means by default the function will call apply() on the editor. A more interesting parameter is the second one. It’s a function literal with a receiver, and the receiver is of type SharedPreferences.Editor. It means that when calling this function, we can use lambda over the receiver, so we can directly call functions that are exposed by the receiver type without additional qualifiers. This is shown with the putString call.

Operating on view before drawing Most of the apps we are using every day are having some sort of lists where some images are being loaded. Often, those images are of a different size, and the image sizes are usually provided in the response. Since the images are normally loaded in the background, we want to allocate the space required for the image to be displayed, and once it’s loaded we already have the space allocated, so we would avoid UI expanding when the image is being displayed, which prevents the UI flickering effect. This is usually done by using a ViewTreeObserver which provides an OnPreDrawListener. This listener has a callback that is being called before the view drawing. For our images example, usually we set the view sizes that are provided in the response inside this callback, and we apply some default background (for example gray). That is one of the many use cases of the ViewTreeObserver observer and its OnPreDrawListener. Here is a snippet that shows the way we normally approach it:

view.viewTreeObserver.addOnPreDrawListener(
    object : ViewTreeObserver.OnPreDrawListener {
        override fun onPreDraw(): Boolean {
            viewTreeObserver.removeOnPreDrawListener(this)
            performSomethingOverTheView()
            return true
        }
    })
複製代碼

Android KTX has defined an extension function on the View type named doOnPreDraw() that simplifies the above snippet, so it would become:

view.doOnPreDraw {
     performSomethingOverTheView()
}
複製代碼

There are also some other nice extensions defined on the View type which are working with the view visibility, updating view padding or layout params etc.

Working with bundle Bundles are a very common thing in Android, but working with bundles is often quite a boilerplate. The way we normally compose a bundle looks like this:

val bundle = Bundle()
bundle.putString("key", "value")
bundle.putString("keyBoolean", true)
bundle.putString("keyInt", 1)
複製代碼

Android KTX defines a top-level function named bundleOf(), and by using it, composing bundle becomes a lot nicer:

val bundle = bundleOf("key" to "value", "keyBoolean" to true, "keyInt" to 1)
複製代碼

There is also a persistableBundleOf() function for creating a PersistableBundle but it’s available for API version 21 and up. Similarly, there is a contentValuesOf() function that could be used in the same way as the functions for creating bundle above, and it returns a ContentValues object.

Iteration over view group Working with ViewGroup in Android is quite a common thing. The ViewGroup is a kind of container that could contain other views called children. Many times we need to loop through its children, but the traditional way to do so could be quite a mess. Android KTX has defined an extension property that exposes its children, and here is how it looks like:

val ViewGroup.children: Sequence<View>
    get() = object : Sequence<View> {
        override fun iterator() = this@children.iterator()
    }
複製代碼

As we can see, the children property is returning a sequence of child views, and it allows us to write very concise loops over any ViewGroup type:

viewGroup.children.forEach {
    doSomething(it)
}
複製代碼

Displaying toast One of the most common ways of displaying some sort of short info to the user is a Toast. When displaying a toast by using the standard API, we have to pass the Context, the actual message that we want to be displayed, whether it is a String or a resource reference to load value from the strings.xml, and the toast duration. Since we cannot display a toast without having a Context, and since most of the time the toast duration is its Toast.LENGTH_SHORT constant, it would be great to have an option to display a toast by simply calling a function and passing the actual message argument. A common way to achieve this is by defining some sort of base class that defines such method, and then it would be called from the subclasses. However, Android KTX defines an extension functions to the Context type for displaying toasts. It takes 2 arguments, the message and the duration, while the duration is being set to Toast.LENGTH_SHORT by default. So we can call to display toast wherever we have a Context by simply passing the message argument, whether it is a String type or a reference to the string resources:

toast("Hello")
toast(R.string.hello)
複製代碼

Wrap Up Android KTX is a very nice part of the Android JetPack project, and as we’ve seen it contains quite some nice ways to improve the Android development we are used to. As told before, it doesn’t provide any new functionalities, but rather simplifies the APIs which are already provided by the Android SDK. Here is a nice talk from the Google I/O 2018 by Jake Wharton, where he elaborates more on the Android KTX. This article only scratches the surface, and there are many more fun things to be revealed inside the Android KTX, related to animation, database, location, graphics, text and so on. Also, the team is welcoming new contributions, which is great, so if you have an idea that is not there yet, feel free to submit it.

相關文章
相關標籤/搜索