Some practical tips from Kotlin


1.Lazy Loading

Lazy loading has several benefits. Lazy loading can make programs start faster because loading is delayed until variables are accessed. This is especially useful in Android applications that use Kotlin instead of server applications. For Android apps, we naturally want to reduce app startup time so that users can see the app content more quickly, rather than waiting for the initial load screen.

Lazy loading is also more efficient memory because we only need to call resources to load them into memory. Such as:

val gankApi: GankApi by lazy {
    val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(API_URL)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
    retrofit.create(GankApi::class.java)
}
Copy the code

If the user never calls GankApi, it is never loaded. Therefore, it does not take up the required resources.

Lazy loading also works well for package initialization:

val name: String by lazy {
    Log.d(TAG, "executed only first time")
    "Double Thunder"
}
Copy the code

If you’re not worried about multithreading or want to improve performance, you can use it

lazy(LazyThreadSafeMode.NONE){ ... } 
Copy the code

2. Customize Getters/Setters

Kotlin automatically uses the getter/setter model, but there are also cases (such as Json) where we need to use custom getters and setters. Such as:

@ParseClassName("Book")
class Book : ParseObject() {

    // getString() and put() are methods that come from ParseObject
    var name: String
        get() = getString("name")
        set(value) = put("name", value)

    var author: String
        get() = getString("author")
        set(value) = put("author", value)
}
Copy the code

3. Lambdas

button.setOnClickListener { view ->
    startDetailActivity()
}

toolbar.setOnLongClickListener { 
    showContextMenu()
    true
}

Copy the code

4.Data Classes

The data Class is a simple version of Class that automatically adds methods including equals(), hashCode(), copy(), and toString(). Separate data from business logic.

data class User(val name: String, val age: Int)
Copy the code

If you parse Json data classes using Gson, you can use the default constructor:

// Example with Gson's @SerializedName annotation
data class User(
    @SerializedName("name") val name: String = "",
    @SerializedName("age") val age: Int = 0
)
Copy the code

5. Set filtering

val users = api.getUsers()
// we only want to show the active users in one list
val activeUsersNames = items.filter { 
    it.active // the "it" variable is the parameter for single parameter lamdba functions
}
adapter.setUsers(activeUsers)
Copy the code

6. What is the Object expression?

Object Expressions allow you to define singletons. Such as:

package com.savvyapps.example.util

import android.os.Handler
import android.os.Looper

// notice that this is object instead of class
object ThreadUtil {

    fun onMainThread(runnable: Runnable) {
        val mainHandler = Handler(Looper.getMainLooper())
        mainHandler.post(runnable)
    }
}
Copy the code

ThreadUtil calls static class methods directly:

ThreadUtil.onMainThread(runnable)
Copy the code

In a similar way, we create objects instead of anonymous inner classes:

viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
    override fun onPageScrollStateChanged(state: Int) {}

    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}

    override fun onPageSelected(position: Int) {
        bindUser(position)
    }
});
Copy the code

Both are basically the same thing – creating a class as a single instance of the declared object.

7. Companion Object

Kotlin has no static variables and methods. Correspondingly, you can use companion objects. The companion object allows constants and methods to be defined, similar to static in Java. With it, you can follow the fragment pattern of newInstance.

class ViewUserActivity : AppCompatActivity() { companion object { const val KEY_USER = "user" fun intent(context: Context, user: User): Intent { val intent = Intent(context, ViewUserActivity::class.java) intent.putExtra(KEY_USER, user) return intent } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_cooking) val user = intent.getParcelableExtra<User>(KEY_USER) //... }}Copy the code

We are familiar with the use:

val intent = ViewUserActivity.intent(context, user)
startActivity(intent)
Copy the code

8.Global Constants (Global Constants)

Kotlin allows global constants that span the entire application. In general, a constant should minimize its scope, but this is a good way to do it when the constant is needed globally.

const val PRESENTATION_MODE_PRESENTING = "presenting"
const val PRESENTATION_MODE_EDITING = "editing"
Copy the code

9.Optional Parameters

Optional arguments make method calls more flexible without having to pass NULL or default values. For example, when defining an animation:

fun View.fadeOut(duration: Long = 500): ViewPropertyAnimator {return animate().alpha(0.0f).setDuration(duration)} icon.fadeout () // fade out with default time (500) icon.fadeOut(1000) // fade out with custom timeCopy the code

10. Extensions

For example: hide the keyboard in the Activity call

fun Activity.hideKeyboard(): Boolean { val view = currentFocus view? .let { val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager return inputMethodManager.hideSoftInputFromWindow(view.windowToken, InputMethodManager.HIDE_NOT_ALWAYS) } return false }Copy the code

Recommend a site to collect Extensions. kotlinextensions.com

11. lateinit

Checking for Null is one of Kotlin’s features, so when data is defined, the data is initialized. But there are some properties in Android that need to be initialized in the onCreate() method.

private lateinit var mAdapter: RecyclerAdapter<Transaction>

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   mAdapter = RecyclerAdapter(R.layout.item_transaction)
}

Copy the code

If it is an underlying data type:

var count: Int by Delegates.notNull<Int>()
var name:String by Delegate()
Copy the code

If using Butter Knife:

@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ButterKnife.bind(this)
        // you can now reference toolbar with no problems!
        toolbar.setTitle("Hello There")
}
Copy the code

12. Safe Typecasting

Secure type conversions are required in Android. When you first do the conversion in Kotlin, you can do this:

var feedFragment: FeedFragment? = supportFragmentManager
    .findFragmentByTag(TAG_FEED_FRAGMENT) as FeedFragment
Copy the code

But in practice this can only lead to a crash. When “as” is called, it converts objects, but if the converted object is “null”, an error is reported. The correct way to use it is “as?” :

var feedFragment: FeedFragment? = supportFragmentManager
    .findFragmentByTag(TAG_FEED_FRAGMENT) as? FeedFragment
if (feedFragment == null) {
    feedFragment = FeedFragment.newInstance()
    supportFragmentManager.beginTransaction()
            .replace(R.id.root_fragment, feedFragment, TAG_FEED_FRAGMENT)
            .commit()
}
Copy the code

13. Let operator

“Let” operator: this method is allowed if the value of the object is not empty.

//Java if (currentUser ! = null) { text.setText(currentUser.name) } //instead Kotlin user?.let { println(it.name) }Copy the code

14. isNullOrEmpty | isNullOrBlank

We need to verify multiple times when developing Android applications. If you’re not using Kotlin to handle this problem, you’ve probably already found the TextUtils class in Android.

if (TextUtils.isEmpty(name)) {
    // alert the user!
}
public static boolean isEmpty(@Nullable CharSequence str) {
    return str == null || str.length() == 0;
}
Copy the code

If both names are Spaces, textutils.isempty is not sufficient for use. IsNullorBlank is available.

public inline fun CharSequence? .isNullOrEmpty(): Boolean = this == null || this.length == 0 public inline fun CharSequence? .isNullOrBlank(): Boolean = this == null || this.isBlank() // If we do not care about the possibility of only spaces... if (number.isNullOrEmpty()) { // alert the user to fill in their number! } // when we need to block the user from inputting only spaces if (name.isNullOrBlank()) { // alert the user to fill in their name! }Copy the code

15. Avoid abstract methods of the Kotlin class

Also use lambdas whenever possible. This leads to cleaner, more intuitive code. For example, in Java, click listening is:

public interface OnClickListener {
    void onClick(View v);
}
Copy the code

Used in Java:

view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // do something
    }
});
Copy the code

And in Kotlin:

View. setOnClickListener {view -> // do something} view.setOnClickListener() { // do something }Copy the code

If Kotlin is using a single abstract method:

view.setOnClickListener(object : OnClickListener {
    override fun onClick(v: View?) {
        // do things
    }
})
Copy the code

Here’s another way:

private var onClickListener: ((View) -> Unit)? = null fun setOnClickListener(listener: (view: View) -> Unit) { onClickListener = listener } // later, to invoke onClickListener? .invoke(this)Copy the code

16. With the function

With is a very useful function that is included in Kotlin’s standard library. It takes an object and an extension function as its arguments, and then makes the object extend the function. This means that all of the code we write in parentheses is an extension function for the object (the first argument), and we can use all of its public methods and attributes just as we would for this. This is great for simplifying code when we’re doing multiple operations on the same object.

with(helloWorldTextView) {
    text = "Hello World!"
    visibility = View.VISIBLE
}
Copy the code

17. Static Layout Import

One of the most commonly used pieces of code in Android is to use findViewById() to retrieve the corresponding View.

There are some solutions, such as the Butterknife library, that can save a lot of code, but Kotlin takes another step and allows you to import all references to a view from an imported layout.

For example, this XML layout:

<? The XML version = "1.0" encoding = "utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/tvHelloWorld" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </RelativeLayout>Copy the code

In the Activity:

/ / import the corresponding XML import kotlinx. Android. Synthetic. Main. Activity_main. * class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) setContentView(r.laiout.activity_main) // Use tvHelloWorld.text = "Hello World!" }}Copy the code

18. Implement POJO classes with Kotlin

In Java

public class User { private String firstName; private String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; }}Copy the code

In Kotlin, this can be simplified to:

class User {
   var firstName: String? = null
   var lastName: String? = null
}
Copy the code

Reduce AsyncTash usage

Use with Anko Lib. Switching between background and main thread is particularly intuitive and simple. UiThread runs on the main thread, and we don’t need to care about the Activity’s lifecycle (pause and Stop), so there are no errors.

doAsync {
    var result = expensiveCalculation()
    uiThread {
        toast(result)
    }
}
Copy the code

Apply the function

It looks similar to with, but with a slight difference. Apply can be used to avoid creating a Builder, because the function that the object calls can initialize itself as needed, and then apply returns the same object:

user = User().apply {
    firstName = Double
    lastName = Thunder
}
Copy the code