A.? And!!!!! Lateinit and let

Kotlin’s type system is designed to eliminate NullPointerExceptions from our code.

1.1?

Kotlin proposes an nul-safe concept based on Java’s null pointer, in which every property cannot be null by default.

Such as:

var a: String = "test kotlin"
a = null // Compile error
Copy the code

If we want to allow nullability, we need to manually declare a variable as an nullable String, write String?

var a: String? = "test kotlin"
a = null // Compile successfully
Copy the code

1.2!!!!!

!!!!! Non-empty assertion operator. Converts any value to a non-null type, and throws an exception if the value is null.

object Test {

    vars:String? =null

    @JvmStatic
    fun main(args: Array<String>){ println(s!! .length) } }Copy the code

Executing the above code throws the following exception.

Exception in thread "main" kotlin.KotlinNullPointerException
Copy the code

When the App is about to be released, we will check to avoid using “!!” Instead consider using lateinit or let functions instead.

1.3 lateinit

In a class, if some member variables cannot be initialized in the first place, and you do not want to use nullable types (i.e. with? Type). Then, use lateinit to describe it.

A variable modified by lateinit is not uninitialized, it needs to be acquired or initialized in the lifecycle process.

If access to an uninitialized lateinit variable causes a UninitializedPropertyAccessException.

1.4 let function

The let function takes the current object as the it argument to the closure and returns the last line in the function, or specifies return. It looks a little bit like the run function.

The let function differs from the run function in that it refers to the object within the function.

/** * Calls the specified function [block] with `this` value as its argument and returns its result. */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)}Copy the code

With? In combination, the let function can execute code inside the function when the object is not null, thus avoiding null-pointer exceptions.

It is usually used like this:

? .let { .... }Copy the code

In Developing Android Apps efficiently using Kotlin (part 2), I introduced a way to use the run and apply functions together. In fact, it uses “!!” There are hidden dangers.

        viewModel.email.run {

            if(value!! .isEmpty()) { toast(resources.getString(R.string.you_have_not_completed_the_email_address)).show()return@onClickRight
            }
            if(! Util.checkEmail(value!!) ) { toast(resources.getString(R.string.the_email_format_you_have_filled_is_incorrect)).show()return@onClickRight
            }

            viewModel
        }.subject.run {

            if(value!! .isEmpty()) { toast(resources.getString(R.string.you_have_not_completed_the_feedback_subject)).show()return@onClickRight
            }

            viewModel
        }.content.apply {

            if(value!! .isEmpty()) { toast(resources.getString(R.string.you_have_not_completed_the_details)).show()return@onClickRight}}Copy the code

The let function can be optimized to avoid the null pointer situation.

        viewModel.email.run {
            value?.let {
                if (it.isEmpty()) {
                    toast(string(R.string.you_have_not_completed_the_email_address)).show()
                    return@onClickRight
                }
                if(! Util.checkEmail(it)) { toast(string(R.string.the_email_format_you_have_filled_is_incorrect)).show()return@onClickRight
                }
            }

            viewModel
        }.subject.run {
            value?.let {
                if (it.isEmpty()) {
                    toast(string(R.string.you_have_not_completed_the_feedback_subject)).show()
                    return@onClickRight} } viewModel }.content.apply { value? .let {if (it.isEmpty()) {
                    toast(string(R.string.you_have_not_completed_the_details)).show()
                    return@onClickRight}}}Copy the code

2. Default arguments to the function

In Kotlin, functions can have default arguments, eliminating the need to write a long list of overloaded functions for default arguments, as in Java.

For example, when we use RxBinding, we might write a Transformer to prevent UI controls from being clicked repeatedly

    /** * Prevent double click Transformer */
    @JvmStatic
    fun <T> preventDuplicateClicksTransformer(windowDuration:Long=1000,timeUnit: TimeUnit=TimeUnit.MILLISECONDS): ObservableTransformer<T, T> {
        return ObservableTransformer { upstream ->
            upstream.throttleFirst(windowDuration, timeUnit)
        }
    }
Copy the code

You cannot click on a UI control repeatedly for 1 second, so you can write this because you are using the default parameters.

        RxView.clicks(textview)
                .compose(RxJavaUtils.preventDuplicateClicksTransformer())
                .subscribe({
                         ......
                })
Copy the code

Use of DSL

Last year, I wrote an article about Kotlin DSLS — using Kotlin for DSL-style programming, which I personally find much simpler and more intuitive.

In the project, I tried to encapsulate the TOAST and Glide frameworks using a DSL approach. The previous usage was Kotlin’s extension function, but for the time being, the other members of the team prefer chained calls.

3.1 Packaging of Glide

Glide extension functions can be used to meet the project.

/** * Placeholder rectangle */
fun ImageView.load(url: String?). {
    get(url).placeholder(R.drawable.shape_default_rec_bg)
            .error(R.drawable.shape_default_rec_bg)
            .into(this)}/** * Placeholder rounded rectangle */
fun ImageView.loadRound(url: String? , centerCrop:Boolean = false) {
    get(url).placeholder(R.drawable.shape_default_round_bg)
            .error(R.drawable.shape_default_round_bg)
            .transform(RoundedCornersTransformation(DisplayUtil.dp2px(context, 10f), 0, centerCrop = centerCrop))
            .into(this)}/** * Placeholder circle */
fun ImageView.loadCircle(url: Drawable?). {
    get(url).placeholder(R.drawable.shape_default_circle_bg)
            .apply(RequestOptions.circleCropTransform())
            .error(R.drawable.shape_default_circle_bg)
            .into(this)}fun ImageView.loadCircle(url: String?). {
    get(url).placeholder(R.drawable.shape_default_circle_bg)
            .apply(RequestOptions.circleCropTransform())
            .error(R.drawable.shape_default_circle_bg)
            .into(this)}fun ImageView.get(url: String?).: GlideRequest<Drawable> = GlideApp.with(context).load(url)
fun ImageView.get(url: Drawable?).: GlideRequest<Drawable> = GlideApp.with(context).load(url)
Copy the code

When you load an image, make it look like a rounded rectangle

holder.itemView.iv_game.loadRound(image_url)
Copy the code

Use a DSL for encapsulation

class GlideWrapper {

    var url:String? = null

    varimage: ImageView? =null

    var placeholder: Int = R.drawable.shape_default_rec_bg

    var error: Int = R.drawable.shape_default_rec_bg

    var transform: Transformation<Bitmap>? = null

}

fun load(init: GlideWrapper. () -> Unit) {

    val wrap = GlideWrapper()

    wrap.init()

    execute(wrap)
}

private fun execute(wrap:GlideWrapper){ wrap.image? .let {var request = it.get(wrap.url).placeholder(wrap.placeholder).error(wrap.error)

        if(wrap? .transform! =null) {

            request.transform(wrap.transform!!)
        }

        request.into(it)

    }

}
Copy the code

Again, load the image and make it look like a rounded rectangle

            load {
                url = image_url
                image = holder.itemView.iv_game
                transform = RoundedCornersTransformation(DisplayUtil.dp2px(context, 10f), 0, centerCrop = false)}Copy the code

3.2 Encapsulation of TOAST

Prompts are essential for any App, and in our project we also used extension functions to encapsulate toast.

fun Toast.setGravityCenter(a): Toast {
    setGravity(Gravity.CENTER, 0.0)
    return this
}

/** * Set Toast font and background color *@param messageColor
 * @param backgroundColor
 * @return* /
fun Toast.setToastColor(@ColorInt messageColor: Int.@ColorInt backgroundColor: Int) {
    val view = view
    if(view ! =null) {
        val message = view.findViewById(android.R.id.message) as TextView
        message.setBackgroundColor(backgroundColor)
        message.setTextColor(messageColor)
    }
}

/** * Set Toast font and background *@param messageColor
 * @param background
 * @return* /
fun Toast.setBackground(@ColorInt messageColor: Int = Color.WHITE, @DrawableRes background: Int = R.drawable.shape_toast_bg): Toast {
    val view = view
    if(view ! =null) {
        val message = view.findViewById(android.R.id.message) as TextView
        view.setBackgroundResource(background)
        message.setTextColor(messageColor)
    }
    return this
}

//@SuppressLint("ShowToast")
fun toast(text: CharSequence): Toast = Toast.makeText(App.instance, text, Toast.LENGTH_LONG)
        .setGravityCenter()
        .setBackground()
// Call withErrorIcon where needed. Do not add it by default
// .withErrorIcon()


//@SuppressLint("ShowToast")
fun toast(@StringRes res: Int): Toast = Toast.makeText(App.instance, App.instance.resources.getString(res), Toast.LENGTH_LONG)
        .setGravityCenter()
        .setBackground()
// Call withErrorIcon where needed. Do not add it by default
// .withErrorIcon()

fun Toast.withErrorIcon(@DrawableRes iconRes: Int = R.drawable.ic_toast_error): Toast {
    val view = view
    if(view ! =null) {
        val layout = this.view as LinearLayout
        val layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
        val icon = ImageView(getApplicationContext())
        icon.setImageResource(iconRes)
        icon.setPadding(0.0, Util.dip2px(8f), 0) icon.layoutParams = layoutParams layout.orientation = LinearLayout.HORIZONTAL layout.gravity = Gravity.CENTER_VERTICAL  layout.addView(icon,0)}return this
}

fun Toast.withSuccIcon(@DrawableRes iconRes: Int = R.drawable.ic_right_circle): Toast {
    val view = view
    if(view ! =null) {
        val layout = this.view as LinearLayout
        val layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
        val icon = ImageView(getApplicationContext())
        icon.setImageResource(iconRes)
        icon.setPadding(0.0, Util.dip2px(8f), 0) icon.layoutParams = layoutParams layout.orientation = LinearLayout.HORIZONTAL layout.gravity = Gravity.CENTER_VERTICAL  layout.addView(icon,0)}return this
}
Copy the code

To show an error, you might write something like this.

toast(resources.getString(R.string.you_have_not_completed_the_email_address)).withErrorIcon().show()
Copy the code

Use a DSL for encapsulation

class ToastWrapper {

    var text:String? = null

    var res:Int? = null

    var showSuccess:Boolean = false

    var showError:Boolean = false
}

fun toast(init: ToastWrapper. () -> Unit) {
    val wrap = ToastWrapper()

    wrap.init()

    execute(wrap)
}

private fun execute(wrap:ToastWrapper) {

    vartaost:Toast? =nullwrap.text? .let { taost = toast(it) } wrap.res? .let { taost = toast(it) }if(wrap.showSuccess) { taost? .withSuccIcon() }else if(wrap.showError) { taost? .withErrorIcon() } taost? .show() }Copy the code

The same error message is displayed using a DSL.

                toast {

                    res = R.string.you_have_not_completed_the_email_address
                    showError = true
                }
Copy the code

conclusion

So far, the articles in this series have been loosely organized, more like common tips.

The DSL in this article is still used in combination with extension functions, which I consider to be further encapsulation. I still prefer DSL to chaining calls.

Related articles in this series:

Use Kotlin to efficiently develop Android App(5) the final part

Developing Android Apps efficiently with Kotlin (part 4)

Developing Android Apps efficiently with Kotlin (part 2)

Developing Android Apps efficiently with Kotlin (1)


Java and Android technology stack: weekly updates push original technical articles, welcome to scan the public qr code below and follow, look forward to growing with you and progress together.