preface
When I saw the DSL recently, I thought that it could take advantage of some of Kotlin’s features to simplify the code, so let’s see how it works.
The body of the
It might be a bit jarring for those unfamiliar with Kotlin to start by saying the principles, so I’m going to go through them from the beginning.
convention
Kotlin’s convention is definitely something we use in our development, but we don’t pay close attention to it. The idea of a convention is to call a specially named function using a different and more concise notation than the normal method call syntax.
Two key points are extracted here, one for more concise symbolic calls and one for specially named functions. In plain English, it makes function calls simpler.
For example, the most familiar set and call [index] instead of get(index), let’s define our own class to implement this convention:
Data class TestBean(val name: String,val age: Int){// Use the overloaded operator get method operator fun get(index: Int): Any{ return when(index) { 0 -> name 1 -> age else -> name } } }Copy the code
Then when we use:
Val testBean = testBean ("zyh",20) testbean.get (0) testBean[0]Copy the code
Invoke conventions
As with the get convention above, [] is a cleaner way to call a GET method. Here’s the invoke convention, which causes an object to call a method like a function. Here’s a straightforward example:
Data class TestBean(val name: String,val age: Int){// Invoke invoke() : String{ return "$name - $age" } }Copy the code
With the above code defined, let’s use it:
Val testBean = testBean("zyh",20) // Invoke testBean()Copy the code
Here you’ll see that testBean objects can call the Invoke method normally, but testBean() can also call the Invoke method directly. This is what the Invoke convention does to make invoking the Invoke method easier.
Invoke convention and functional types
Now that we know about the Invoke convention, let’s combine it with lambda.
We know that a function type is actually a class that implements the FunctionN interface, and then when the function type is a function type, we pass it a lambda, which is compiled into the anonymous inner class of FunctionN (non-inline, of course), The call to lambda then becomes an invoke call to the FunctionN interface.
Here’s an example code:
Class TestInvoke {private var mSingleListener: (Int) -> Unit)? Public fun setSingleListener(listener:(Int) -> Unit)? {this.msingLelistener = listener} // invoke mSingleListener? .invoke(100) // Invoke if (mSingleListener! = null){ mSingleListener!! (100)}}}Copy the code
After defining the above callback variable, we use this callback, since we know that higher-order functions are classes that implement the FunctionN interface, i.e.
// Invoke public interface Function1<in P1, out R> : Function<R> { /** Invokes the function with the specified argument. */ public operator fun invoke(p1: P1): R }Copy the code
Then I can pass the argument directly using the following code:
val function1 = object: Function1<Int,Unit> {
override fun invoke(p1: Int) {
Logger.d("$p1")
}
}
testInvoke.setSingleListener(function1)
Copy the code
This seems reasonable because in the testRun function we call invoke with 100 as an argument, and that 100 is then called back to function1, but what about when we pass lambda:
val testInvoke = TestInvoke()
testInvoke.setSingleListener { returnInt ->
Logger.d("$returnInt")
}
Copy the code
The above code passing a lambda has the same effect as passing an instance of a class, except that it is a block of code that does not invoke anything, so this is a feature. When a lambda is called as an argument to a function, it can be treated as an automatic call to invoke.
Invoke in DSL practice: Gradle dependency
The invoke dependency is a good use in some DSLS. Let’s look at the use of Gradle dependencies.
It is common to see the following code:
Dependencies {implementation 'androidx. Core: the core - KTX: 1.6.0' implementation 'androidx. Appcompat: appcompat: 1.3.1' / /... }Copy the code
This is something we’re used to, and it feels more like a configuration item than it does like code, and this is actually a piece of code, but in this style. How to implement this style? Let’s implement it briefly:
Class DependencyHandler{funcompile (libString: String){logger. d("add $libString")} invoke(body: DependencyHandler.() -> Unit){body()}}Copy the code
After the above code is written, we can have the following three calls:
Val dependency = DependencyHandler() // invoke dependency. Invoke {compile("androidx.core:core-ktx:1.6.0") Dependency.com compile("androidx.core:core-ktx:1.6.0") // Lambda dependency{compile("androidx.core:core-ktx:1.6.0")}Copy the code
The third method is the one common in Gradle configuration files. There are only two key points: define invoke and define a lambda with a receiver, leaving out this.
conclusion
In fact, the invoke convention and the method for writing lambdas with recipients are becoming more and more popular. For example, the anko library and now the compose library are both written declaratively in this way. After looking at the principle, you will find that it is actually quite convenient.
When I started working with Compose, I added another wave.
The tutorials
Android Basics tutorial:
Android Basic Course U- Summary _bilibili _bilibili
Android Basic Course UI- Layout _bilibili _bilibili
Android Basic course UI- controls _bilibili _bilibili
Android Basic course UI- Animation _bilibili _bilibili
Android Basic course – The use of activity _bilibili _bilibili
Android Basic course -Fragment using methods _bilibili bilibili
Android Fundamentals – Hot Repair/Hot Update Technology Principles _ Bilibili _Bilibili
This article is from juejin.cn/post/704702… , if there is infringement, please contact to delete.