Chuyển đến nội dung chính

Sự khác nhau giữa let, apply, with, run và also trong Kotlin

Với những ai đã sử dụng Kotlin để phát triển ứng dụng, chắc hẳn đã không ít lần sử dụng các standard functions run, with, let, also và apply. Để hiểu và sử dụng thành thục chúng không phải là dễ. Và dưới đây là những điều đúc kết lại được.

Scoping functions

Có thể hiểu đơn giản, scoping function là phạm vi ảnh hưởng nhất định của một hàm. Nó là điều cốt lõi để phân biệt giữa các scoping functions: run, with, T.run, T.let, T.also và T.apply.
Dưới đây là minh hoạ phạm vi của hàm run:
fun test() {
    var mood = "I am sad"

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}
Ở trên, ta có thể thấy rõ ràng trong phạm vi của hàm run, biến mood đã được định nghĩa lại trước khi in ra mà không làm ảnh hưởng tới phần khác của chương trình

3 attributes of scoping functions

1.Normal vs. extension function

Chúng ta sẽ cùng làm thử một ví dụ với with và T.run. Hai hàm này thực sự tương tự nhau, chỉ có một điểm khác biệt duy nhất là with - một normal function và T.run - một extension function.
with(webview.settings) {
    this.javaScriptEnabled = true
    this.databaseEnabled = true
}
// similarly
webview.settings.run {
    javaScriptEnabled = true
    databaseEnabled = true
}
Bình thường sẽ chẳng có gì để xem xét ở đây cả. Nhưng nếu ta giả sử webview ở trên có thể có giá trị null thì sao. Nếu vậy bắt buộc chúng ta phải kiểu tra trước khi gắn giá trị cho các thuộc tính của nó:
with(webview.settings) {
    this?.javaScriptEnabled = true
    this?.databaseEnabled = true
}

// similarly.
webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}
Về mặt hình thức, rõ ràng T.run trông có vẻ "mượt" hơn, chỉ cần kiểu tra null một lần duy nhất trước khi gắn giá trị cho các thuộc tính; còn với hàm with, mỗi lần định gắn giá trị cho thuộc tính bất kỳ nào đó, ta lại phải tự hỏi xem nó có thể null được hay không!

2. This vs. it argument

Lần này ta sẽ lấy T.run và T.let làm ví dụ minh họa.
stringVariable?.run {
    println("The length of this String is ${this.length}")
}
// Similarly.
stringVariable?.let {
    println("The length of this String is ${it.length}")
}
Hãy nhìn vào tham số của chúng để thấy sự khác biệt. Trong T.run, block là T.() vì vậy mà trong phạm vi của hàm, T có thể được truyền vào như this. Với T.let, nó gửi chính nó vào block: (T). Vì vậy mà T có thể được gọi ra trong hàm như it.

3. Return this vs. other type

Tiếp theo sẽ là T.let và T.also. Nếu chỉ nhìn vào phạm vi của hàm bên trong, ta sẽ chẳng thấy sự khác nhau của chúng ở đâu cả.
stringVariable?.let {
    println("The length of this String is ${it.length}")
}
// Exactly the same as below
stringVariable?.also {
    println("The length of this String is ${it.length}")
}
Tuy nhiên, khác biệt là giá trị trả lại của chúng. T.also trả về bản thân T, this, trong khi đó T.let cho phép trả về một kiểu giá trị khác. Dưới đây là minh họa việc sử dụng T.let để trả về một giá trị khác mong muốn:
val original = "abc"
// Evolve the value and send to the next chain
original.let {
    println("The original String is $it") // "abc"
    it.reversed() // evolve it as parameter to send to next let
}.let {
    println("The reverse String is $it") // "cba"
    it.length  // can be evolve to other type
}.let {
    println("The length of the String is $it") // 3
}
Cũng với đoạn code trên, nếu thay đổi bằng also ta sẽ không được kết quả đúng nữa:
original.also {
    println("The original String is $it") // "abc"
    it.reversed() // useless
}.also {
    println("The reverse String is ${it}") // "abc"
    it.length  // useless
}.also {
    println("The length of the String is ${it}") // "abc"
}
Cách sửa lại cho đúng
original.also {
    println("The original String is $it") // "abc"
}.also {
    println("The reverse String is ${it.reversed()}") // "cba"
}.also {
    println("The length of the String is ${it.length}") // 3
}
T.let có vẻ như giúp chúng ta kết hợp thành một khối chức năng đồng nhất như ở ví dụ trên mà không phải rườm ra như khi sử dụng T.also. Tuy nhiên, chúng ta vẫn có thể thấy T.also có một số điểm lợi thế khác như:
  • Nó bóc tách rõ ràng từng phần chức năng nhỏ riêng biệt.
  • Nó có thể tự thao tác trước khi được sử dụng
Và khi kết hợp cả hai T.let và T.also, một hàm sẽ thay đổi chính nó, một hàm sẽ duy trì chính nó, chúng sẽ đem lại những đoạn code ngắn gọn và dễ hiểu hơn:
// Normal approach
fun makeDir(path: String): File  {
    val result = File(path)
    result.mkdirs()
    return result
}

// Improved approach
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }

Looking at all attributes

Ở trên ta đã tìm hiểm được về 4 hàm run, with, Tlet và T.also. Vậy còn T.apply thì như thế nào T.apply có 3 tính chất bên dưới:
  • Nó là một extension function
  • this là tham số
  • Và nó cũng trả lại chính no, this
// Normal approach
fun createInstance(args: Bundle) : MyFragment {
    val fragment = MyFragment()
    fragment.arguments = args
    return fragment
}

// Improved approach
fun createInstance(args: Bundle) = MyFragment().apply { arguments = args }

//Or we could also making unchained object creation chain-able.

// Normal approach
fun createIntent(intentData: String, intentAction: String): Intent {
    val intent = Intent()
    intent.action = intentAction
    intent.data=Uri.parse(intentData)
    return intent
}

// Improved approach, chaining
fun createIntent(intentData: String, intentAction: String) =
        Intent().apply { action = intentAction }
                .apply { data = Uri.parse(intentData) }

Function selections

Dựa vào các thuộc tính và tính năng của các , chúng ta sẽ quyết định sử dụng dùng hàm nào cho hợp lý.


Bảng so sánh

Tôi đã thực hiện một sơ đồ so sánh đơn giản để bao gồm các đặc tính và hy vọng chúng ta có thể thấy sự khác biệt dễ dàng hơn:
NameIs Extension Function?Return TypeArgument in blockblock definition
also()yesT (this)explicit it(T) -> Unit
apply()yesT (this)implicit thisT.() -> Unit
let()yesR (from block body)explicit it(T) -> R
run()yesR (from block body)implicit thisT.() -> R
with()noR (from block body)implicit thisT.() -> R

Nhận xét

Bài đăng phổ biến từ blog này

Kích thước icon cho app Android và công cụ tạo icon của Google

Để hiển thị chuẩn theo các size màn hình thì chúng ta sẽ theo các kích thước sau: 36 × 36 (ldpi) – Low 48 × 48 (mdpi) – Medium 72 × 72 (hdpi) – High 96 × 96 (x-hdpi) – x-high 144 × 144 (xx-hdpi) 192 × 192 (xxx-hdpi) 512 × 512 (Google Play store) -> Kích thước này để làm ảnh demo cho App khi upload lên store. Khi tạo icon launcher cho app nếu tạo bằng các  Launcher icon generator  cửa Google thì khi cài vào điện thoại nó sẽ bé hơn so với các app khác vì google tự động cho thêm padding vào icon. Tránh điều này thì nên tự thiết kế bằng Photoshop sau đó dùng  cái này  để tạo thì sẽ to và đẹp hơn, nó tạo nhanh và đủ các kích thước chuẩn như bên trên kia. Nếu bạn muốn bo góc thì cũng làm bo góc ở trong photoshop trước sau đó mới dùng công cụ bên trên. Kiến thức liên quan đến đơn vị đo trong Android: pixel có thể hiểu là số điểm ảnh có trong 1 dot có hình vuông vì là ảnh bitmap mà. Ảnh đen trắng binary image thì 1 dot = 1 px = 1 bit (chỉ có trạng thá...

Get You Last known Location & Current Location using FusedLocationProviderClient

NOVEMBER 30, 2017 Get You Last known Location & Current Location using FusedLocationProviderClient        I would like to cover very basic and simple example of retreiving last known location & Current Location  using Fused location API.  I have written many example for fused location API in my previous posts but in this post i will show how to get retrieve the location  without implementing Google Api Client . All these magics happen after the release of version  11.0.0 of Google Play services SDK. FusedLocationProviderApi is Deprecated We have previously used following way to retrieve the last known location. mLastLocation  =  LocationServices. FusedLocationApi . getLastLocation ( mGoogleApiClient ) ; We no need to define LocationServices. FusedLocationApi  anymore. it is deprecated. It will be removed in the future release. Instead You can use LocationServi...

Cấu trúc cơ bản layout trong Flutter