[create an app from scratch] kotlin

Time:2021-7-25

If a worker wants to do well, he must sharpen his tools first. If we start an app from scratch, choosing the most appropriate language is the primary task. If you are as helpless as I am about Java’s faltering gait and rigid syntax, kotlin won’t disappoint you to a large extent. Although in order to comply with the JVM specification and compatible with Java, it introduces some more complex concepts and syntax, so many students give up getting started. In fact, the deeper you go, the more you can’t stop. In addition to Android development, bloggers often use kotlin coding on the back end, and sometimes use Java mixing for some reasons. In general, it can reduce the amount of code and improve production efficiency. It seems that the code structure is clearer. If you don’t have kotlin experience, but have compared Java and C #, you will understand what I mean. Even kotlin is more convenient than C #. It can be said that kotlin not only has c# convenient syntax, but also relies on the good ecology of Java platform. What are you hesitating about?

Basics

var: variable, is a variable variable. We know that the VaR type attribute cannot be set as the deferred load attribute becauselazyThere is no setValue (…) method in. In di scenario, it is often associated withlateinitFor use with, seeExplanation of lateinit variable in kotlin at bytecode level
val: immutable, a read-only variable. In additionconst val, only allowed in top level and object. Their differences are as follows:

  • Const Val visibility is public final static and can be accessed directly.
  • Val visibility is private final static, and val generates methodsgetNormalObject(), accessed through method calls.

Unit: when a function has no return value, we use unit to represent this feature, which is the same as void in Java.

open: it is allowed to create any subclass and override any method in Java, unless the displayed is marked with the final keyword. This is not the case in the world of kotlin. In kotlin, all its classes are final by default, which means that they cannot be inherited, and all methods in the class are final by default, so even kotlin’s methods cannot be overridden by default. Add open to the class, and the class can be inherited; Add open to the method, and the method can be overridden.

inlineKotlin inline function。 It copies the code block to the calling place, reducing the number of calling layers and the generation of additional objects.
crossinline: This is a keyword introduced due to the side effect of inline. Because inline copies the code to the calling place, if there is a return in the code, the logic of the target code (caller) may be destroyed. usecrossinlineModify the corresponding lambda and return to the corresponding label [, instead of the whole method].
reified: in order to deal with the problem of code redundancy caused by Java pseudo generics. SeeMaking generics simpler and safer with kotlin reified。 This is mainly to deal with the problems in JavaGeneric erase。 Generics in Java are pseudo generics, that is, its generics only exist in the compilation time. The generated bytecode file does not contain any generic information (but at least the type mismatch can be found early in the compilation time). In the compiled bytecode file, it has been replaced with the original raw type (raw type / object), And the forced transformation code is inserted in the corresponding place to erase the type. Therefore, for the runtime Java language, ArrayListWith ArrayListIt’s the same type. In contrast, the generics used in C # are true generics. Their generics exist in the program source code, compiled IL or runtime CLRAnd listThere are two different types, which have their own virtual method table and type data.
The following is my code for encapsulating rabbitmq consumer monitoring (interested students can refer to my blogRabbitmq Getting Started GuideFor more information:

/**
     *Get the message from the specified queue and define the callback (for kotlin)
     *
     * @param queue the name of the queue from where receive messages
     * @param block callback when a message arrived
     */
    inline fun  receive(queue: String, crossinline block: (T) -> Boolean) {
        factory.newConnection().use {
            val conn = it.get()
            val channel = conn.createChannel()
            channel.basicConsume(queue, false, object : DefaultConsumer(channel) {
                override fun handleDelivery(consumerTag: String?, envelope: Envelope, properties: AMQP.BasicProperties?, body: ByteArray) {
                    try {
                        val message = JSON.parseObject(String(body), object : TypeReference() {})
                        val done = block(message)
                        if (done) {
                            channel.basicAck(envelope.deliveryTag, false)
                        } else {
                            //If it fails, it will be delivered again. Otherwise, it will be discarded or delivered to the dead letter queue (if configured)
                            channel.basicNack(envelope.deliveryTag, false, !envelope.isRedeliver)
                        }
                    } catch (e: Exception) {
                        _ Logger. Error ("error - ${e.message} while processing message - ${string (body)}")
                        throw e
                    }
                }
            })
        }
    }

Note that the java compiler does not supportinlineandreifiedSo if you want to use Java calls, you need to write another version of for Java.

field: used for attribute value / assignment logic (if explicitly defined), similar to the value keyword in c# attribute, to prevent the stackoverflowerror exception of program crash caused by self recursion of accessor. SeeKotlin learning – field

[email protected]: an anonymous inner class object references an outer object [containing it].

byModify attributes and fields to provide several functions. SeeKotlin by
It can also be used in class definition. You can delegate all public methods of an instance to this class [, as if these methods were defined in this class]. This should becombinationBut we can also use it to achieve a certain degree of grammar"Multiple inheritance", take the code snippet in the following collaboration part as an example:

class BasicCorotineActivity : AppCompatActivity(), CoroutineScope by MainScope() {}

amongCoroutineScopeIt’s the interface,MainScope()The implementation class of coroutinescope is returnedContextScopeYesexample。 In other words, the basiccorotineactivity implements the interface coroutinescope, but the basiccorotineactivity itself does not implement the methods, but delegates them to the object returned by mainscope (). This reduces code redundancy. From the perspective of writing, it seems that basiccorotineactivity inherits both appcompatactivity class and coroutinescope instance:)

In kotlininterfaceYou can not only declare functions, but also implement them. The only difference from classes is that they are stateless, so properties need to be overridden by subclasses. Class needs to be responsible for saving the state of interface properties.

Elvis operator:?:, similar to | in JS. If the former is null, the latter is taken.

Kotlin is not a pure language. It often considers the compatibility and convertibility of Java in the syntax part, and adds many syntax and keywords that confuse novices. For example, when annotating an attribute or a main constructor parameter, the kotlin element will generate multiple corresponding Java elements, so the annotation has multiple possible positions in the Java bytecode. If you want to specify exactly how to generate the annotation, you can use the following syntax:

class Example(@field:Ann val foo,    // annotate Java field
              @get:Ann val bar,      // annotate Java getter
              @param:Ann val quux)   // annotate Java constructor parameter

For more information, seeAnnotations of kotlin coding tips

companion objectandobject: kotlin removes the concept of static. Both of them have static singleton patterns after they are converted to Java, which is easy to confuse the difference between them. In fact, it is clear from the use scenario analysis that the former is used as a static internal singleton class [object] (company means partner), and the latter is a static singleton class [object], which does not need the existence of peripheral classes (there is no company).
We often use it in the company object scenario@JvmStaticand@JvmFieldIn order to expose the methods and fields they modify [in the view of external java code] as children of the class, seeMicro knowledge #1 kotlin’s @ jvmstatic and @ jvmfield annotations
Related concepts: @ jvmoverloads
The object keyword can also be used to create anonymous objects of interfaces or abstract classes.

Kotlin allows you to define top-level functions and properties in files.

Kotlin hasExtension method, andExtended properties, seeExtension properties and extension methods of kotlin

Kotlin’s function arguments are read-only.


lambda

Kotlin has a lot of grammatical sugars, such aslambda expressions , there are several different ways to pass as parameters:

  1. Common method:button.setOnClickListener({strInfo: String -> Unit})
  2. If the last argument is a passed lambda expression, you can specify it outside the parentheses:button.setOnClickListener(){strInfo: String -> Unit}
  3. If the function has only one parameter [or all other parameters have default values], and the parameter is lambda, the parentheses can be omitted:button.setOnClickListener{strInfo: String -> Unit}
  4. It can even be omitted as:button.setOnClickListener{strInfo}

Taking the above example as an example, if the parameter accepted by setonclicklistener is not a lambda type but an interface, and there is only one method under the interface, the above syntax can also be used [, it seems that the parameter accepted by setonclicklistener is a lambda type]. Such interfaces are commonly used@FunctionalInterfaceEmbellishment( In fact, this should be the feature of Java, such asRxJavaMediumsubscribe(Consumer super T> onNext), you can pass lambda expressions directly when others call it.
Note that the interface written by kotlin does not support this feature

Moving the lambda method body outside the parentheses when calling should be for the readability of the code, making it closer to the feeling of code logic block rather than a single parameter. SeeUse of let, with, run, apply and also functions of kotlin seriesThe signature definitions of these extension functions in. By the way, learn about the usage scenarios of these functions.


Coroutine

First of all, we need to know one thing,Synergetic processThis concept is now a little abused. The popular languages on the market seem to want to incorporate the collaborative process into their own characteristics. If you don’t know about the collaborative process, please refer to the blog written by the bloggerLet’s talk about the process againOr other information. Bloggers believe that the real collaborative process is the implementation of go. Although kotlin also has coroutines, it is more similar to async / await in c# and is syntax processing at the multi-threaded level. For more in-depth analysis, seeIs kotlin collaboration really more efficient than Java threads?

suspend: keyword, which is generally identified at the beginning of a function to indicate that the function is a time-consuming operation. The main function of this keyword is toAs a reminder, the function will not run to a child thread immediately because this keyword is added. Whether to switch threads is still determined bylaunchwithContextasyncDecided. Of course, sometimes we must add suspend before the function if other suspend functions are called inside the function.

If usedretrofit2If you encapsulate the network request, the interface definition should returnCall<>(if any) type. Or it can be written by Jake WhartonCoroutineCallAdapterFactoryComponent, which enables functions to supportDeferred<>Return value to simplify the development of synergy + retrofit2. However, since retrofit 2.6.0, retrofit has built-in support for the suspend keyword. Functions can be defined in a more pure way, such as:

@GET("users/{id}")
suspend fun user(@Path("id") id: Long): User

To encapsulate a traditional callback into a coroutine pattern, you can usesuspendCoroutine, as follows:

suspend fun MqttAsyncClient.aPublish(payload: MqttMessage, topic: String): IMqttToken =
        suspendCoroutine { cont ->
            publish(topic, payload, null, object: MqttActionListener {
                override fun onSuccess(asyncActionToken: IMqttToken) {
                    cont.resume(asyncActionToken)
                }

                override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable) {
                    cont.resumeWithException(exception)
                }
            })
        }

Commonly usedCoroutineScope.launchCreate a collaboration (and of courserunBlockingwithContextasyncWait), it will return aJobObject, which is convenient for external control of the collaborative process.

  • job.join(): blocks the current thread until the job is executed. This is a suspend function, so it is generally called in coroutine to block the current coroutine.
  • job.cancel(): cancel the job, and the job will enter after executioncancellingStatus, but whether it is really cancelled depends on the implementation of the job itself. The suspend functions defined in the coroutine standard library support cancel operations (such as delay). When customizing a job, you can use the isactive attribute to determine whether the current task has been cancelled. If it is found that it has been cancelled, stop and continue execution. If a custom job does not have corresponding processing logic, even calling job. Cancel () does not cancel its execution.
  • SupervisorJob(parent: Job? = null): returns a job instance in which the child jobs do not affect each other. If a child job fails, it will not affect the execution of other child jobs. The parent parameter is used to associate its own parent job. If you study the collaborative process source code, you will often seeContextScope(SupervisorJob() + Dispatchers.Main)How to write (e.gViewModel.viewModelScopeThe + sign here isCoroutineContextPair operatorplusBoth are subclasses of coroutinecontext.

Postscript collection

When using public attributes, sometimes a compile time error of “smartcast is impossible because property has open or custom getter” is thrown. The reason is that the compiler analyzes the code and finds that the object returned each time the get attribute is not the same. The solution is very simple. Just define a temporary variable to point to the value obtained by a get. SeeSmartcast is impossible because propery has open or custom getter

Problems caused by Java generic erasure. The following codes work correctly:

private fun  getToken(): Token? {
        val preference = TaoismApplication.appContext.sharedPreferences(SharedPreference.SESSION)
        val json = preference.getString(SharedPreference.TOKEN, "")
        if (!json.isNullOrBlank()) {
            return Gson().fromJson(json, object : TypeToken() {}.type)
        } else {
            return null
        }
    }

Because there are many getxxx() in the code, extract the template code:

private fun  get(key: String): T? {
        val preference = TaoismApplication.appContext.sharedPreferences(SharedPreference.SESSION)
        val json = preference.getString(key, "")
        if (!json.isNullOrBlank()) {
            return Gson().fromJson(json, object : TypeToken() {}.type)
        } else {
            return null
        }
    }

callget(SharedPreference.TOKEN)report errors:java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.xxx.xxx.Token
So, only type information can be explicitly passed in, and the transformation method signature isget(key: String, typeToken: Type)

Kotlin exceptions: all kotlin exceptions are unchecked exceptions. If @ throws is annotated on the function, the compiled Java code will become a checked exception conforming to the Java pattern, that is, the exception types that may be thrown will be explicitly declared on the method definition and need to be handled on the call link.

For the mixed development of kotlin and Java using IntelliJ idea, it is better to store kotlin files and java files in their own folders, otherwise the error that the class cannot be found may be reported at runtime (because the code files that do not belong to this folder and are not referenced by other files will be ignored at compile time). As follows:


reference material

Kotlin collaboration — today, let’s talk about launch and async