Brief introduction: This should be the first article of 2019. There is really a lot of demand for going home one month before the Chinese New Year. As Kang Shao said, almost all the demand for the whole year has been written in the last one or two months. So writing articles have been shelved for a long time, of course, again busy every day will brush gold. I’ve been writing projects with Kotlin for a long time, and to be honest, by now Kotlin is getting more and more comfortable with it (I can only describe it as exhilarating). Of course, this time I still talk about Kotlin, say I need to develop some of their own thinking and practice. One of the most memorable is “Don’t Repeat Yourself.” When you’re constantly writing repetitive code, stop and think about changing the state of things.

Today we’re going to talk about something very, very simple. Callbacks are called callbacks, and they’re often used in Android development and some client-side development. In fact, if the terminal interface development is treated as a black box, it is nothing more than input and output, input data, output UI rendering and user interaction events, then most of the interaction events will be implemented by callback. So here’s how to make your callbacks more kotlin:

  • 1. Callback implementation in Java
  • Use Kotlin to transform callbacks in Java
  • 3. Further make your callbacks Kotlin flavored
  • 4. Compare Object expression callbacks with DSL callbacks
  • 5. Suggestions for using callbacks in Kotlin
  • 6, Don’t Repeat Yourself(DSL callback configuration is too template, why not use AS plugin to automatically generate code)
  • 7, DslListenerBuilder plug-in basic introduction and use
  • DslListenerBuilder plug-in source code and Velocity template engine basic introduction
  • 9,

A Java callback implementation

Callbacks in Java are generally handled by writing an interface and then defining some callback functions in the interface. It then exposes a function that sets the callback interface, passing in an argument that is an instance of the callback interface, usually in the form of an anonymous object. For example, take the OnClickListener and TextWatcher source code in Android:

  • Java implementation of the OnClickListener callback
/ / an OnClickListener definition
public interface OnClickListener {
    void onClick(View v);
}

public void setOnClickListener(OnClickListener listener) {
    this.clickListener = listener;
}

/ / the use of an OnClickListener
mBtnSubmit.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
	    //add your logic code}});Copy the code
  • Java implementation of the TextWatcher callback
/ / TextWatcher definition
public interface TextWatcher extends NoCopySpan {
    public void beforeTextChanged(CharSequence s, int start,int count, int after);
    
    public void onTextChanged(CharSequence s, int start, int before, int count);

    public void afterTextChanged(Editable s);
}

public void addTextChangedListener(TextWatcher watcher) {
    if (mListeners == null) {
        mListeners = new ArrayList<TextWatcher>();
    }

    mListeners.add(watcher);
}

/ / TextWatcher use
mEtComment.addTextChangedListener(new TextWatcher() {
	@Override
	public void beforeTextChanged(CharSequence s, int start, int count, int after) {
             //add your logic code
	}

	@Override
	public void onTextChanged(CharSequence s, int start, int before, int count) {
            //add your logic code
	}

	@Override
	public void afterTextChanged(Editable s) {
            //add your logic code}});Copy the code

Use Kotlin to transform callbacks in Java

For the above Java callback writing method, it is estimated that most people will do the following processing after Kotlin:

1. If the interface has only one callback function, you can use the lamBA expression directly to achieve the callback shorthand.

If there are multiple callbacks in an interface, they will be implemented using the object expression.

Take the example of modifying the above code:

  • 1, (only a callback function shorthand)OnClickListener callback Kotlin transformation
// There is only one common shorthand for the callback function: the use of OnClickListener
mBtnSubmit.setOnClickListener { view ->
    //add your logic code
}

// Set the use of the onClick extension function in the Coroutine Coroutine framework for the OnClickListener listener listener
mBtnSubmit.onClick { view ->
    //add your logic code
}

//Coroutine framework: onClick extension function definition
fun android.view.View.onClick(
        context: CoroutineContext = UI,
        handler: suspend CoroutineScope. (v: android.view.View?). -> Unit
) {
    setOnClickListener { v ->
        launch(context) {
            handler(v)
        }
    }
}

Copy the code
  • 2, (multiple object callback expressions)TextWatcher callback Kotlin transformation (object expression)
mEtComment.addTextChangedListener(object: TextWatcher{
    override fun afterTextChanged(s: Editable?). {
       //add your logic code
    }

    override fun beforeTextChanged(s: CharSequence? , start:Int, count: Int, after: Int) {
       //add your logic code
    } 

    override fun onTextChanged(s: CharSequence? , start:Int, before: Int, count: Int) {
       //add your logic code}})Copy the code

About the object expression implementation of Kotlin callback, there are many Kotlin friends in the public account comments to me, I feel that this writing is directly from the Java translation of the same, completely do not see where the advantages of Kotlin. Ask me if there is a more Kotlin way to write it, of course there is, please read on.

3. Further make your callbacks Kotlin flavored (DSL configuration callbacks)

In fact, if you read the source code of the Koltin project from many foreign leaders, you will find that they rarely use object expression to implement the callback. Instead, they use another way to implement the callback, and the overall writing looks more Kotlin flavor. Even if you use an object expression internally, exposing it to the outer layer does a layer of DSL configuration transformation in the middle, making external calls more kotlinized. Take Github in the MaterialDrawer project (currently has 1W more than star) in the officially designated MatrialDrawer project Kotlin version implementation of the MaterialDrawerKt project in the middle of the source code as an example:

  • 1. DrawerImageLoader callback definition
// Note: The function argument is a lambda expression with a return value
public fun drawerImageLoader(actions: DrawerImageLoaderKt. () -> Unit): DrawerImageLoader.IDrawerImageLoader {
    val loaderImpl = DrawerImageLoaderKt().apply(actions).build() //
    DrawerImageLoader.init(loaderImpl)
    return loaderImpl
}

//DrawerImageLoaderKt: DSL Listener Builder class
public class DrawerImageLoaderKt {
    // Define the function lamba member object to call back
    private varsetFunc: ((ImageView, Uri, Drawable? , String?) ->Unit)? = null
    private var placeholderFunc: ((Context, String?) -> Drawable)? = null

    internal fun build(a) = object : AbstractDrawerImageLoader() {

        private valsetFunction: (ImageView, Uri, Drawable? , String?) ->Unit= setFunc ? :throw IllegalStateException("DrawerImageLoader has to have a set function")

        private valplaceholderFunction = placeholderFunc ? : { ctx, tag ->super.placeholder(ctx, tag) }

        override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable? , tag:String?). = setFunction(imageView, uri, placeholder, tag)

        override fun placeholder(ctx: Context, tag: String?). = placeholderFunction(ctx, tag)

    }

    // A callback function that is exposed to an external call, similar to the setter and getter methods in the build class
    public fun set(setFunction: (imageView: ImageView, uri: Uri, placeholder: Drawable? , tag:String?). -> Unit) {
        this.setFunc = setFunction
    }

    public fun placeholder(placeholderFunction: (ctx: Context, tag: String?). -> Drawable) {
        this.placeholderFunc = placeholderFunction
    }
Copy the code
  • 2, DrawerImageLoader callback use
 drawerImageLoader {
   // The internal callback function can be optionally overridden
    set { imageView, uri, placeholder, _ ->
        Picasso.with(imageView.context)
               .load(uri)
               .placeholder(placeholder)
               .into(imageView)
        }
        
    cancel { imageView ->
        Picasso.with(imageView.context)
               .cancelRequest(imageView)
    }
}
Copy the code

As you can see, the dSL-configured callbacks are much more Kotlin flavored, making the entire callback look very comfortable and much more than smooth.

4. Basic steps of DSL configuration callback

Implementing a DSL configuration callback in one of Kotlin’s classes is very simple in three main steps:

  • 1, define a callback Builder class, and define the callback lamBA expression object members in the class, and finally define the Builder class member functions, these functions are exposed to the external callback functions. Individuals tend to think of it as an inner class of a class. Something like this
class AudioPlayer(context: Context){
     //other logic ...
     
     inner class ListenerBuilder {
        internal var mAudioPlayAction: ((AudioData) -> Unit)? = null
        internal var mAudioPauseAction: ((AudioData) -> Unit)? = null
        internal var mAudioFinishAction: ((AudioData) -> Unit)? = null
    
        fun onAudioPlay(action: (AudioData) -> Unit) {
            mAudioPlayAction = action
        }
    
        fun onAudioPause(action: (AudioData) -> Unit) {
            mAudioPauseAction = action
        }
    
        fun onAudioFinish(action: (AudioData) -> Unit) {
            mAudioFinishAction = action
        }
    }
}

Copy the code
  • 2. Then, declare a reference to an instance of ListenerBuilder in the class and expose a method to set the instance object. Note that the function takes a lamBA with the value returned by ListenerBuilder, as follows:
class AudioPlayer(context: Context){
      //other logic ...
      
     private lateinit var mListener: ListenerBuilder
     fun registerListener(listenerBuilder: ListenerBuilder. () -> Unit) {// LamBA with ListenerBuilder return value
        mListener = ListenerBuilder().also(listenerBuilder)
     }
}     
Copy the code
  • 3. Finally, trigger the corresponding event to call lamBA in the Builder instance
class AudioPlayer(context: Context){
      //other logic ...
     val mediaPlayer = MediaPlayer(mContext)
        mediaPlayer.play(mediaItem, object : PlayerCallbackAdapter() {
            override fun onPlay(item: MediaItem?). {
                if(::mListener.isInitialized) { mListener.mAudioPlayAction? .invoke(mAudioData) } }override fun onPause(item: MediaItem?). {
                if(::mListener.isInitialized) { mListener.mAudioPauseAction? .invoke(mAudioData) } }override fun onPlayCompleted(item: MediaItem?). {
                if(::mListener.isInitialized) { mListener.mAudioFinishAction? .invoke(mAudioData) } } }) }Copy the code
  • 4. External calls
val audioPlayer = AudioPlayer(context)
    audioPlayer.registerListener {
       // You can choose any function you want to call back. You don't need to rewrite it completely
        onAudioPlay {
            //todo your logic
        }

        onAudioPause {
           //todo your logic
        }

        onAudioFinish {
           //todo your logic}}Copy the code

Do you find that DSL callback configuration is more Kotlin than object expression callback? Maybe it looks good, but you don’t know how it works, because it’s too syntactically saccharifying, so let’s take the icing off of it.

5. Unicing the syntax of DSL callback configurations

  • 1. Principle elaboration

The configuration of the DSL callback is actually quite simple. In fact, a Builder class maintains multiple instances of the callback lambda, and then uses the LAMBA feature with the return value instance of the Builder class for the external callback. Within the scope of the lambda, this can be expressed internally as an instance of the Builder class. Invoke its internal-defined member functions using Builder class instances and assign to initialize Builder class callback lambda member instances. These initialized lambda instances perform the Invoke operation when the internal event is triggered. If a member method is not called inside the lambda, then the callback lambda member instance is null in the Builder class. Even if the internal event is fired, empty does not call back to the outside.

In other words, the external callback function block will initialize the Builder class callback lambda instance (in this case, the mXXXAction instance) through the Builder class member function, and then when the internal event is fired, depending on whether the current lambda instance is initialized, if the initialization is completed, I’m just going to execute the lambda immediately and I’m going to execute the block that’s passed in

  • 2. Code disassembly To make the above statement more clear, we can disassemble the code:
mAudioPlayer.registerListener({
    // The registerListener argument is a lambda with the return value of the instance ListenerBuilder
    // So this is internally referred to as the ListenerBuilder instance
    this.onAudioPlay ({  
        //logic block 
    })
    this.onAudioPause ({ 
        // logic block
    })
    this.onAudioFinish({ 
        // logic block})})Copy the code

Let’s take onAudioPlay as an example. Similarly, we call onAudioPlay in ListenerBuilder and pass in a block to initialize the mAudioPlayActionlambda instance in ListenerBuilder. MAudioPlayActionlambda is executed when the onPlay function in the AudioPlayer is called back.

It seems that the object expression callback is useless compared to the DSL callback. Is it possible to discard the object expression? In fact, the expression “object” has its advantages.

Contrast object expression callbacks with DSL callbacks

  • 1, call writing method comparison
// Use DSL to configure callbacks
val audioPlayer = AudioPlayer(context)
    audioPlayer.registerListener {
       // You can choose any function you want to call back. You don't need to rewrite it completely
        onAudioPlay {
            //todo your logic
        }

        onAudioPause {
           //todo your logic
        }

        onAudioFinish {
           //todo your logic}}// Use the object expression callback
val audioPlayer = AudioPlayer(context)
    audioPlayer.registerListener(object: AudioPlayListener{
        override fun onAudioPlay(audioData: AudioData) {
                    //todo your logic
        }
        override fun onAudioPause(audioData: AudioData) {
                    //todo your logic
        }
        override fun onAudioFinish(audioData: AudioData) {
                    //todo your logic}})Copy the code

The call writing contrast clearly feels like DSL configuration is more Kotlin style, so DSL configuration callbacks are better

  • 2, the use of comparison

The obvious advantage of using the DSL is that the callback functions that do not need to be monitored can be omitted. The interface callback must be overwritten if the object expression is implemented directly. Although it can optionally choose the method callback that it needs, it does not avoid a callback Adapter layer. So instead of making an Adapter layer, it’s better to do it all at once. So DSL configuration callbacks are superior

  • 3. Performance comparison

It is easy to see from the way the call is written that the DSL configuration callback creates lambda instance objects for each callback function, whereas the object expression generates only one anonymous object instance, regardless of how many methods there are inside the callback. That’s the difference, so in terms of performance the object object expression is a little bit better, but after asking some of the big guys in the Kotlin community they still prefer the DSL configuration. So both of these approaches are actually good, depending on the needs, you can weigh your options, but I personally like DSL. To verify this, let’s take a look at the decompiled code in both ways and see if it works like this:

// The DSL configuration callback decompiles the code
   public final void setListener(@NotNull Function1 listener) {
      Intrinsics.checkParameterIsNotNull(listener, "listener");
      ListenerBuilder var2 = new ListenerBuilder();
      listener.invoke(var2);
      ListenerBuilder var10000 = this.mListener;
      // Get the instance object corresponding to the AudioPlay method
      Function0 var3 = var10000.getMAudioPlayAction$Coroutine_main();
      Unit var4;
      if(var3 ! =null) {
         var4 = (Unit)var3.invoke();
      }
      // Get the instance object corresponding to the AudioPause method
      var3 = var10000.getMAudioPauseAction$Coroutine_main();
      if(var3 ! =null) {
         var4 = (Unit)var3.invoke();
      }
      // Get the instance object corresponding to the AudioFinish method
      var3 = var10000.getMAudioFinishAction$Coroutine_main();
      if(var3 ! =null) { var4 = (Unit)var3.invoke(); }}//object Object expression decompile code
 public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      int count = true;
      PlayerPlugin player = new PlayerPlugin();
      // New Callback is an instance
      player.setCallback((Callback)(new Callback() {
         public void onAudioPlay(a) {}public void onAudioPause(a) {}public void onAudioFinish(a) {}})); }Copy the code

Don’t Repeat Yourself(so use Kotlin for an automatic ListenerBuilder plugin)

For those of you who have used DSL configuration callbacks and feel that writing them is untechnical and a waste of time, Don’t Repeat Yourself start now. It would be nice if the entire DSL configuration callback process could be automatically generated like toString, setter, and getter methods. So the following is a general introduction to the development of DslListenerBuilder plug-in.

Overall idea of development:

What you do is configure the information parameters through the Swing UI window, generate the template code through the Velocity template engine, and insert the generated code into the current code file through the Intellij Plugin API. So all requirements that require automatic code generation are similar to this process. The next time you need to generate different code, just modify the Velocity template.

Technology points used:

  • 1. Basic Kotlin development knowledge
  • 2. Kotlin extension function
  • 3. Kotlin’s lambda expression
  • 4, Swing UI component development knowledge
  • 5. Basic knowledge of Intellij Plugin development
  • 6, IntelliJ Plugin commonly used development API(Editor, WriteCommandAction, PsiDocumentManager, Document and other API use)
  • #if,#foreach,#set, etc.
  • Basic use of the Velocity template-engine API

Basic introduction and use:

IDEA is a plugin that automatically generates DSL ListenerBuilder callback template code for IDEA, AndroidStudio and JetBrains.

Step 1: Install DslListenerBuilder plug-in according to general IDEA plug-in installation process.

Step 2: Then open a specific class file and place the cursor at the location where the specific code was generated.

Step 3: Use the shortcut key to bring up the panel in Generate and select the “Listener Builder” in it. Then a panel will pop up. You can click the Add button to add one or more callbacks to the LAMBA, or you can select any Item from the panel that you don’t need to delete.

Step 4: Finally click OK to generate the required code at the specified cursor position.

DslListenerBuilder plug-in source code and Velocity template engine learning resources

There are several resources to learn about the Velocity templating engine, as well as the source code on GitHub for more details on the implementation of the plug-in

The plug-in has been uploadedOfficial repository for JetBrains IntelliJ PluginsDslListenerBuilder is still under review. You can install DslListenerBuilder directly in AndroidStudio or IntelliJ IDEA in a few days

DslListenerBuilder plug-in download address

DslListenerBuilder plug-in source address

Velocity template basic syntax

Use the Velocity templatemaking engine to generate code quickly

Ten,

I’ve covered Kotlin callbacks, and I’ve shown you how to develop a plug-in that automatically generates code. The entire plug-in development process applies to other code generation requirements as well. The main reason for writing such a plug-in is that there are so many recent requirements that every time I write a callback, I have to write a lot of similar code. Sometimes when we do something repetitive, it’s good to think about what tools we can use to automate the process. The bottom line: Don’t Repeat Yourself.

Welcome to the Kotlin Developer Alliance, where you can find the latest Kotlin technical articles. We will translate one foreign Kotlin technical article every week. If you like Kotlin, please join us ~~~

Kotlin series articles, welcome to check out:

Effective Kotlin translation series

  • The Effective Kotlin series considers Using primitive arrays for performance optimization (5)
  • Using Sequence To optimize a set (part 4)
  • Exploring the inline modifier in High-order Functions of the Effective Kotlin series
  • When you encounter multiple constructor parameters, consider using the Constructor (part 2)
  • The Effective Kotlin series considers replacing constructors with static factory methods (part 1)

Original Series:

  • Jetbrains developer daily (part 3) Kotlin1.3 new features
  • New features in Kotlin1.3 (Contract and coroutines)
  • JetBrains Developer Daily News (part 1) : a taste of Kotlin/Native
  • How to Overcome the difficulties of Generics in Kotlin
  • How to Overcome the difficulties of Generics in Kotlin (Part 2)
  • How to Overcome generics in Kotlin (Part 1)
  • Kotlin’s unique secret: Reified Type parameters (Part 2)
  • Everything you need to know about the Kotlin property broker
  • Discussion on source analysis of Sequences in Kotlin
  • Complete parsing of Kotlin’s collections and Functional APIS – Part 1
  • A Brief introduction to Kotlin syntax: Complete parsing of the process of compiling lambda into bytecode
  • Complete parsing of Lambda expressions in Kotlin syntax
  • Extension functions in Kotlin syntax
  • A brief introduction to Kotlin syntax: top-level functions, infix calls, and deconstruction statements
  • How to make functions better to call
  • On variables and constants in Kotlin grammar
  • Basic grammar of Kotlin grammar

Translation series:

  • Remember a PR for Kotlin official document translation (inline class)
  • Auto-boxing and High Performance For Inline Classes in Kotlin (part 2)
  • In Kotlin, inline classes are fully parsed.
  • Reified Type parameters (Part 1)
  • When should Type parameter constraints be used in Kotlin generics?
  • A simple way to remember Kotlin parameters and arguments
  • Should Kotlin define functions or attributes?
  • How to remove all of them in your Kotlin code!! (Non-empty assertion)
  • Master Kotlin’s standard library functions: run, with, let, also, and apply
  • Everything you need to know about Kotlin type Aliases
  • Should We use Sequences or Lists in Kotlin?
  • Kotlin’s turtle List rabbit race

Actual combat series:

  • Kotlin ImageSlimming ImageSlimming with Kotlin ImageSlimming
  • Use Kotlin to compress images
  • Use Kotlin wani a picture compression plug-in – actual combat (3)
  • Kotlin actual combat part of the custom View picture rounded corner simple application