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
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