You should know kotlin's practical skills

Posted by vickie on Wed, 05 Jan 2022 20:56:53 +0100

preface

As we all know, kotlin is an android development language pushed by google to replace java
kotlin is easy to use and has a lot of grammar
This paper mainly explains some practical kotlin skills

Custom rounded rectangle

In projects, we often define rounded rectangular backgrounds, which are generally implemented with custom drawable
However, the background and rounded corners of rounded rectangles often change slightly, and once they change, we have to create a new drawable file
This will lead to the problem of file explosion

We can use kotlin's extension function to realize a simple and convenient rounded rectangular background

fun View.setRoundRectBg(color: Int = Color.WHITE, cornerRadius: Int = 15.dp) {
    background = GradientDrawable().apply {
        setColor(color)
        setCornerRadius(cornerRadius.toFloat())
    }
}

For the View that needs to customize the background, you can directly call setRoundRectBg, which is simple and convenient

reified use

Reified, the generic materialization keyword in kotlin, makes abstract things more concrete or real.
Let's give two examples to see how to use reified

startActivity example

We usually write startActivity like this

startActivity(context, NewActivity::class.java)  

We use reified to define an extension function

// Function
inline fun <reified T : Activity> Activity.startActivity(context: Context) {
    startActivity(Intent(context, T::class.java))
}

// Caller
startActivity<NewActivity>(context)

Using reified, simplify generic parameters by adding type passing
This eliminates the need to manually pass generic types

Gson analysis example

Let's first take a look at how we generally use gson to parse json
In a Java serialization Library (such as Gson), when you want to deserialize the JSON string, you must eventually pass the Class object as a parameter so that Gson knows the type you want.

User user = new Gson().fromJson(getJson(), User.class)

Now, let's show the magic of reified type implementation parameters. We will create a very lightweight extension function to wrap the Gson method:

inline fun <reified T> Gson.fromJson(json: String) = 
        fromJson(json, T::class.java)

Now, in our Kotlin code, we can deserialize JSON strings without even passing type information!

val user: User = Gson().fromJson(json)

Kotlin infers the type based on its usage - because we assign it to a variable of User type, kotlin uses it as the type parameter of fromjason()

The kotin interface supports SAM conversion

What is SAM conversion? Some students may not know much about it. Let's first popularize science:

SAM conversion, that is, Single Abstract Method Conversions, refers to the conversion of only a single non default abstract method interface - the interface that meets this condition (called SAM Type) can be directly represented by Lambda in Kotlin - of course, the premise is that the function type shown by Lambda can match the method in the interface.

At Kotlin1 Before 4, Kotlin did not support Kotlin's SAM conversion, but only Java SAM conversion. The official explanation is that Kotlin already has function types and high-order functions, so it does not need to be converted to SAM. Developers don't buy this explanation if you have used Java Lambda and fusion interface. When you switch to Kotlin, you will be confused. It seems that Kotlin is aware of this, or sees the feedback from developers, and finally supports it.

Before 1.4, only one object can be passed, and Kotlin SAM is not supported. After 1.4, Kotlin SAM can be supported, but the usage has changed. Interface needs to be declared with the fun keyword. After you mark an interface with the fun keyword, you can pass lambda as a parameter as long as you take such an interface as a parameter.

// Note that you need to declare with the fun keyword
fun interface Action {
    fun run()
}

fun runAction(a: Action) = a.run()

fun main(){
    // Before 1.4, only object s could be used
    runAction(object : Action{
        override fun run() {
            println("run action")
        }
    })
     // 1.4-M1 supports SAM,OK
    runAction {
        println("Hello, Kotlin 1.4!")
    }
}

entrust

Sometimes the way to do some work is to delegate it to others. This is not to suggest that you entrust your work to your friends, but to entrust the work of one object to another.

Of course, entrustment is not a new term in the software industry. Delegation is a design pattern in which an object delegates a helper object called a proxy to handle requests. The agent is responsible for processing the request on behalf of the original object and making the results available to the original object.

Class delegate

For example, when we want to implement an enhanced ArrayList that supports the recovery of the last deleted item

One way to implement this use case is to inherit the ArrayList class. Since the new class inherits the specific ArrayList class instead of implementing the MutableList interface, it is highly coupled with the implementation of ArrayList.
It would be nice if you only needed to override the remove() function to keep references to deleted items and delegate the remaining empty implementations of MutableList to other objects. To achieve this goal, Kotlin provides a way to delegate most of the work to an internal ArrayList instance and customize its behavior, and introduces a new keyword: by.

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class ListWithTrash <T>(private val innerList: MutableList<T> = ArrayList<T>()) : MutableCollection<T> by innerList {
    var deletedItem : T? = null
    override fun remove(element: T): Boolean {
           deletedItem = element
            return innerList.remove(element)
    }
    fun recover(): T? {
        return deletedItem
    }
}

The by keyword tells Kotlin to delegate the functions of the MutableList interface to an internal ArrayList named innerList. Listwithdash still supports all functions in the MutableList interface by bridging to the internal ArrayList object method. In the meantime, you can now add your own behavior.

Attribute delegate

In addition to class proxies, you can also use the by keyword for property proxies. By using the property proxy, the proxy class is responsible for handling the calls of the corresponding property get and set functions. This feature is very useful when you need to reuse getter/setter logic among other objects, and it also allows you to easily extend the function of simply supporting fields

For example, using delegate properties can encapsulate SharedPreference s
Delegating data store operations to proxy classes has several benefits
1. It simplifies the code and facilitates storage, reading and calling
2. Understand the coupling with the SP. if you want to replace the repository later, you only need to modify the agent class

Call as follows:

object Pref: PreferenceHolder() {
    var isFirstInstall: Boolean by bindToPreferenceField(false)
    var time: Long? by bindToPreferenceFieldNullable()
}

The specific implementation can be seen as follows: Shared preferences should be written like this in Kotlin

LiveData with status

At present, we use MVVM pattern and ViewModel more and more in the development process
We also often use LiveData to identify the status of network requests
We need to define the request start, request success, request failure and three LiveData

In fact, this is also very redundant and repetitive code, so we can encapsulate a LiveData with state

It is defined as follows:

typealias StatefulLiveData<T> = LiveData<RequestState<T>>
typealias StatefulMutableLiveData<T> = MutableLiveData<RequestState<T>>

@MainThread
inline fun <T> StatefulLiveData<T>.observeState(
    owner: LifecycleOwner,
    init: ResultBuilder<T>.() -> Unit
) {
    val result = ResultBuilder<T>().apply(init)

    observe(owner) { state ->
        when (state) {
            is RequestState.Loading -> result.onLading.invoke()
            is RequestState.Success -> result.onSuccess(state.data)
            is RequestState.Error -> result.onError(state.error)
        }
    }
}

Use the following

val data = StatefulMutableLiveData<String>()
viewModel.data.observeState(viewLifecycleOwner) {
            onLading = {
                //loading
            }
            onSuccess = { data ->
                //success
            }
            onError = { exception ->
                //error
            }
        }

Through the above encapsulation, the loading, success and error status of the network request can be encapsulated gracefully and concisely, the code is simplified, and the structure is clear

DSL

DSL (domain specific language), that is, domain specific language: a computer language specialized in solving a specific problem, such as SQL and regular expressions.
However, if an independent language is created to solve a specific domain problem, the development cost and learning cost are very high, so there is the concept of internal DSL. The so-called internal DSL is to build a DSL using a general programming language. For example, Kotlin DSL mentioned in this article, we have a simple definition for Kotlin DSL:

"API developed in Kotlin language, which solves domain specific problems and has a unique code structure."

For example, when we use TabLayout, if we want to add listening to it, we need to implement the following three methods

override fun onTabReselected(tab: TabLayout.Tab?){

}

override fun onTabUnselected(tab: TabLayout.Tab?){

}
    
override fun onTabSelected(tab: TabLayout.Tab?){

}

In fact, we generally only use the ontabs selected method, and the other two are generally empty implementations
We use DSL to encapsulate ontbselectedlistener to avoid writing unnecessary empty implementation code

The specific implementation is as follows:

private typealias OnTabCallback = (tab: TabLayout.Tab?) -> Unit

class OnTabSelectedListenerBuilder : TabLayout.OnTabSelectedListener {

    private var onTabReselectedCallback: OnTabCallback? = null
    private var onTabUnselectedCallback: OnTabCallback? = null
    private var onTabSelectedCallback: OnTabCallback? = null

    override fun onTabReselected(tab: TabLayout.Tab?) =
            onTabReselectedCallback?.invoke(tab) ?: Unit

    override fun onTabUnselected(tab: TabLayout.Tab?) =
            onTabUnselectedCallback?.invoke(tab) ?: Unit

    override fun onTabSelected(tab: TabLayout.Tab?) =
            onTabSelectedCallback?.invoke(tab) ?: Unit

    fun onTabReselected(callback: OnTabCallback) {
        onTabReselectedCallback = callback
    }

    fun onTabUnselected(callback: OnTabCallback) {
        onTabUnselectedCallback = callback
    }

    fun onTabSelected(callback: OnTabCallback) {
        onTabSelectedCallback = callback
    }

}

fun registerOnTabSelectedListener(function: OnTabSelectedListenerBuilder.() -> Unit) =
        OnTabSelectedListenerBuilder().also(function)

General steps to define DSL:

  • 1. First define a class to implement the callback interface and its callback method.
  • 2. Observe the parameters of the callback method, extract it into a function type, and use the type alias to give a nickname to the function type as needed, and modify it with private.
  • 3. Declare some variable (var) private member variables of nullable function type in the class, get the corresponding variable in the callback function, implement its invoke function, and pass in the corresponding parameters.
  • 4. Define some functions in the class with the same name as the callback interface, but the parameter is the corresponding function type, and assign the function type to the corresponding member variable of the current class.
  • 5. Define a member function. The parameter is a Lambda expression with the recipient object of the class we have set and returns Unit. Create the corresponding object in the function and pass in the Lambda expression using the also function.

Call as follows:

tabLayout.addOnTabSelectedListener(registerOnTabSelectedListener {
    onTabSelected { vpOrder.currentItem = it?.position ?: 0 }
})

As above, you can avoid writing some unnecessary empty implementation code

Learning Resource Recommendation

This Alibaba open source kotlin from introduction to mastery helps you learn kotlin better!!!

Attach a screenshot of the data:

1. Ready to start

  • Basic grammar

  • Idioms
  • Coding style

2. Foundation

  • Basic type

  • package
  • control flow
  • Return and jump

3. Classes and objects

  • Classes and inheritance
  • Properties and fields
  • Interface
  • Visibility modifier
  • extend
  • data object
  • generic paradigm

  • Nested class
  • Enumeration class
  • Object expressions and declarations
  • proxy pattern
  • Proxy properties

4. Functions and lambda expressions

  • function

  • Advanced functions and lambda expressions
  • Inline function

5. Others

  • Multiple declarations
  • Ranges
  • Type checking and automatic conversion

  • This expression
  • equation
  • Operator overloading
  • Air safety
  • abnormal
  • annotation
  • reflex
  • Dynamic type

6. Reference

  • interactive quality

7. Tools

  • Kotlin code documentation
  • Using Maven
  • Using Ant

  • Using Griffon
  • Using Gradle

8.FAQ

  • Comparison with java
  • Comparison with Scala

This full version of "kotlin from introduction to Mastery" PDF e-book, friends can send a private letter or comment 888 if necessary, and I will share it with you for free.

Related videos:

[2021 latest version] Android studio installation tutorial + Android zero foundation tutorial video (suitable for Android 0 foundation and introduction to Android) including audio and video_ Beep beep beep_ bilibili

Android advanced learning: Kotlin core technology_ Beep beep beep_ bilibili

[advanced Android tutorial] - Analysis of hot repair Principle_ Beep beep beep_ bilibili

[advanced Android tutorial] - how to solve OOM problems and analysis of LeakCanary principle_ Beep beep beep_ bilibili

Topics: Android Programmer kotlin