1. 程式人生 > >Listeners with several functions in Kotlin. How to make them shine?

Listeners with several functions in Kotlin. How to make them shine?

One question I get often is how to simplify the interaction with listeners that have several functions on Kotlin. For listeners (or any interfaces) with a single function is simple: it automatically lets you replace it by a lambda. But that’s not the case for listeners with several functions.

So in this article I want to show you different ways to deal with the problem, and you may even learn some

new Kotlin tricks on the way!

The problem

When we’re dealing with listeners, let’s say the OnclickListener for views, thanks to optimizations that Kotlin do over Java libraries, we can turn this:

12345 view.setOnClickListener(object:View.OnClickListener{override fun onClick(v:View?){toast("View clicked!")}})

into this:

1 view.setOnClickListener{toast("View clicked!")}

The problem is that when we get used to it, we want it everywhere. But this doesn’t escalate when the interface has several functions.

For instance, if we want to set a listener to a view animation, we end up with this “nice” code:

Want to learn Kotlin?

Check my free guide to create your first project in 15 minutes!

12345678910111213141516171819 view.animate().alpha(0f).setListener(object:Animator.AnimatorListener{override fun onAnimationStart(animation:Animator?){toast("Animation Start")}override fun onAnimationRepeat(animation:Animator?){toast("Animation Repeat")}override fun onAnimationEnd(animation:Animator?){toast("Animation End")}override fun onAnimationCancel(animation:Animator?){toast("Animation Cancel")}})

You may argue that the Android framework already gives a solution for it: the adapters. For almost any interface that has several methods, they provide an abstract class that implements all methods as empty. In the case above, you could have:

1234567 view.animate().alpha(0f).setListener(object:AnimatorListenerAdapter(){override fun onAnimationEnd(animation:Animator?){toast("Animation End")}})

Ok, a little better, but this have a couple of issues:

  • The adapters are classes, which means that if we want a class to act as an implementation of this adapter, it cannot extend anything else.
  • We get back to the old school days, where we need an anonymous object and a function to represent something that it’s clearer with a lambda.

What options do we have?

Interfaces in Kotlin: they can contain code

Remember when we talked about interfaces in Kotlin? They can have code, and as such, you can declare adapters that can be implemented instead of extended (you can do the same with Java 8 and default methods in interfaces, in case you’re using it for Android now):

123456 interfaceMyAnimatorListenerAdapter:Animator.AnimatorListener{override fun onAnimationStart(animation:Animator)=Unitoverride fun onAnimationRepeat(animation:Animator)=Unitoverride fun onAnimationCancel(animation:Animator)=Unitoverride fun onAnimationEnd(animation:Animator)=Unit}

With this, all functions will do nothing by default, and this means that a class can implement this interface and only declare the ones it needs:

123456 classMainActivity:AppCompatActivity(),MyAnimatorListenerAdapter{...override fun onAnimationEnd(animation:Animator){toast("Animation End")}}

After that, you can just use it as the argument for the listener:

123 view.animate().alpha(0f).setListener(this)

This solution eliminates one of the problems I explained at the beginning, but it forces us to still declare explicit functions for it. Missing lambdas here?

Besides, though this may save from using inheritance from time to time, for most cases you’ll still be using the anonymous objects, which is exactly the same as using the framework adapters.

But hey! This is an interesting idea: if you need an adapter for listeners with several functions, better use interfaces rather than abstract classes. Composition over inheritance FTW.

Extension functions for common cases

Let’s move to cleaner solutions. It may happen (as in the case above) that most times you just need the same function, and not much interested in the other. For AnimatorListener, the most used one is usually onAnimationEnd. So why not creating an extension function covering just that case?

123 view.animate().alpha(0f).onAnimationEnd{toast("Animation End")}

That’s nice! The extension function is applied to ViewPropertyAnimator, which is what animate(), alpha, and all other animation functions return.

1234567 inline fun ViewPropertyAnimator.onAnimationEnd(crossinline continuation:(Animator)->Unit){setListener(object:AnimatorListenerAdapter(){override fun onAnimationEnd(animation:Animator){continuation(animation)}})}

I’ve talked about inline before, but if you still have some doubts, I recommend you to take a look at the official reference.

As you see, the function just receives a lambda that is called when the animation ends. The extension does the nasty work for us: it creates the adapter and calls setListener.

That’s much better! We could create one extension function per function in the listener. But in this particular case, we have the problem that the animator only accepts one listener. So we can only use one at a time.

In any case, for the most repeating cases (like this one), it doesn’t hurt having a function like this. It’s the simpler solution, very easy to read and to understand.

Using named arguments and default values

But one of the reasons why you and I love Kotlin is that it has lots of amazing features to clean up our code! So you may imagine we still have some alternatives. Next one would be to make use of named arguments: this lets us define lambdas and explicitly say what they are being used for, which will highly improve readability.

We can have a function similar to the one above, but covering all the cases:

123456789101112131415161718192021222324 inline fun ViewPropertyAnimator.setListener(crossinline animationStart:(Animator)->Unit,crossinline animationRepeat:(Animator)->Unit,crossinline animationCancel:(Animator)->Unit,crossinline animationEnd:(Animator)->Unit){setListener(object:AnimatorListenerAdapter(){override fun onAnimationStart(animation:Animator){animationStart(animation)}override fun onAnimationRepeat(animation:Animator){animationRepeat(animation)}override fun onAnimationCancel(animation:Animator){animationCancel(animation)}override fun onAnimationEnd(animation:Animator){animationEnd(animation)}})}

The function itself is not very nice, but that will usually be the case with extension functions. They’re hiding the dirty parts of the framework, so someone has to do the hard work. Now you can use it like this:

12345678 view.animate().alpha(0f).setListener(animationStart={toast("Animation start")},animationRepeat={toast("Animation repeat")},animationCancel={toast("Animation cancel")},animationEnd={toast("Animation end")})

Thanks to the named arguments, it’s clear what’s happening here.

You will need to make sure that nobody uses this without named arguments, otherwise it becomes a little mess:

12345678 view.animate().alpha(0f).setListener({toast("Animation start")},{toast("Animation repeat")},{toast("Animation cancel")},{toast("Animation end")})

Anyway, this solution still forces us to implement all functions. But it’s easy to solve: just use default values for the arguments. Empty lambdas will make it:

12345678 inline fun ViewPropertyAnimator.setListener(crossinline animationStart:(Animator)->Unit={},crossinline animationRepeat:(Animator)->Unit={},crossinline animationCancel:(Animator)->Unit={},crossinline animationEnd:(Animator)->Unit={}){...}

And now you can do:

12345 view.animate().alpha(0f).setListener(animationEnd={toast("Animation end")})

Not bad, right? A little more complex than the previous option, but much more flexible.

The killer option: DSLs

So far, I’ve been explaining simple solutions, which honestly may cover most cases. But if you want to go crazy, you can even create a small DSL that makes things even more explicit.

The idea, which is taken from how Anko implements some listeners, is to create a helper which implements a set of functions that receive a lambda. This lambda will be called in the corresponding implementation of the interface. I want to show you the result first, and then explain the code that makes it real:

相關推薦

Listeners with several functions in Kotlin. How to make them shine?

One question I get often is how to simplify the interaction with listeners that have several functions on Kotlin. For listeners (or any interfaces) w

Reified Types in Kotlin: how to use the type within a function (KAD 14)

One of the limitations that most frustrates Java developers when using generics is not being able to use the type directly. Normally this is solved b

【 InkGenius】Good developers who are familiar with the entire stack know how to make life easier for those around

Good developers who are familiar with the entire stack know how to make life easier for those around

How to make HTTP Post request with JSON body in Swift

Try this, // prepare json data let json: [String: Any] = ["title": "ABC", "dict": ["1":"First", "2":"Second"]] let jsonDat

How to make the impossible possible in CSS with a little creativity

CSS Previous sibling selectors don’t exist, but that doesn’t mean we can’t use themIf you ever used CSS sibling selectors, you know there’s only two. The +

How to make unit test on Android with Kotlin (KAD 22)

Of course, Kotlin also allows us to do unit tests in a very simple way, and very similar to what we’re used in Java. There are some small complicatio

How to make a GroupBox in website development by VS.NET2005

Sometimes we need to make a GroupBox on my webpage.Using the HTML object(fieldset ,legend)  we can make it out! source: <fieldset style

How to make your iOS apps more secure with SSL pinning

swift 和 obj-c 完成 ssl 的寫法如下: We can start by instantiating an NSURLSession object with the default session configuration. Swift self.urlSession = NSURLSes

[iOS] How to make a Global function in Swift

You can create a custom class with the method you need like this: class MyScene: SKScene { func CheckMusicMute() { if InGameMusicOnOff == tr

Form is submitted when I click on the button in form. How to avoid this?

原來button 沒加 type 屬性,會變成 submit @[email protected]; 太久沒寫網頁,這個變化真大。 A button element with no type attribute specified represents the same thing as a butto

How to Make a Profit in a Bear Market?

How to Make a Profit in a Bear Market?2018 has been a tough year for crypto investors. Bitcoin has declined by more than 60% and leading altcoins have lost

How To Make My Python Code Shorter With Function.

Python function is very important in structuring your code in a program. When it comes to writing a simple program, you can do it without functions, b

5 Common Mistakes in Website Testing and How to Avoid Them

Here at Sentient, we are well acclimated to the world of website experimentation. Whether you are a conversion specialist, marketer, or ecommerce manag

Top 3 Reasons Why Chatbots Fail in Finance [And How to Fix Them]

We use cookies to give you the best online experience. By using our website you agree to our use of cookies in accordance with our cookie

How to Make Blogging Easier with Artificial Intelligence

Mike Kaput is a senior consultant at PR 20/20 who is passionate about AI's potential to transform marketing. At PR 20/20, he creates measurable marketing r

How to Make Predictions with scikit

Tweet Share Share Google Plus How to predict classification or regression outcomes with scikit-l

Critical Values for Statistical Hypothesis Testing and How to Calculate Them in Python

Tweet Share Share Google Plus In is common, if not standard, to interpret the results of statist

Ninja Functions in Kotlin. Understanding the power of generics (KAD 12)

The combined use of several Kotlin features mixed with the use of generics allow to create functions that will greatly simplify your code, while main

Extension functions in Kotlin: Extend the Android Framework (KAD 08)

Extension functions are a really cool feature that Kotlin provides, and that you’ll find yourself using a lot when writing Android Apps. We have to a

How To Make A Swipeable Table View Cell With Actions – Without Going Nuts With Scroll Views

Make a swipeable table view cell without going nuts with scroll views! Apple introduced a great new user interface scheme in the iOS 7 Mail app – sw