Android Programming In Kotlin: Events
Written by Mike James   
Monday, 14 May 2018
Article Index
Android Programming In Kotlin: Events
Functions and Lambdas in Kotlin

Passing Functions In Kotlin

We have examined the fundamental way of passing an object that hosts event handlers as methods, but there are three different, although related, ways of passing an event handler when the event object is a SAM, i.e. only defines a single event handler:

  • Function References

  • Anonymous Functions

  • Lambda Expressions

Of the three, the lambda expression is the most commonly used and encountered.

Notice that Kotlin provides other ways to define and work with functions including function types, extension function, infix functions and more.

Function References

Kotlin supports functions that don’t belong to an object. In fact, the flexibility of Kotlin’s approach to functions is one the reasons for wanting to use it.

So in Kotlin it is perfectly valid to write:

fun myFunction(){
  …
}

outside of a class definition. Of course, to stay compatible with Java this top-level function is in fact a method of a class, but one that is generated by the compiler to have a name that is derived from the package and the file name.

You can explicitly set the name of the class used to contain top-level functions with the

@file:JvmName("class name")

annotation. Notice also that Kotlin lets you call the top-level or package level function without having to give the class name.

That is:

myFunction()

is legal.

Kotlin also has a function reference operator :: which can be used to pass almost any function as a parameter in another function or store a reference to the function in a variable. This makes it possible to use any function as an event handler.

For example, if you define the function at the package level:

fun clickEvent(v: View?) {
 (v as Button).text = "You Clicked Me"
}

then you can write:

b.setOnClickListener(::clickEvent)

If the function is defined as a method within a class you have to write:

b.setOnClickListener(this::clickEvent)

The latest version of Kotlin will allow you to drop the this for method references.

Notice that even though this looks as if you are using and passing a reference to a function, what is happening is that the function is being converted into an instance of the SAM that is specified by the parameter’s type. That is, it is not a reference to a function that is passed, but an object constructed using the function. Each time you pass the function, a new object is constructed from the function. Most of the time this doesn’t make any difference but you need to be aware of it if you are using the function reference operator to pass a function multiple times. Each time you use it another object implementing the SAM is created and this uses memory faster than you might expect.

Anonymous Functions

An anonymous function is exactly what its name suggests – a function with no name defined. You simply write:

fun(parameters){body of function}

You can store a reference to an anonymous function in a variable of the correct type and pass it to another function. For example:

b.setOnClickListener(fun(v: View?) {
            (v as Button).text = "You Clicked Me"
          }
)

You don’t have to use the reference operator because the compiler understands that you want to pass the anonymous function. It converts the anonymous function to an instance of the SAM specified by the parameter’s type. You could save the anonymous function in a variable and then pass the variable.

For example:

val clickEvent=fun(v: View?) {
          (v as Button).text = "You Clicked Me"
         }
b.setOnClickListener(clickEvent)

It is difficult to see any advantage of doing this, however, other than if you are using the same function more than once.

The Lambda

First, what is a lambda?

A lambda is a function that you can define using special notation.

From this point of view, you don’t really need lambda as you can do everything you want to using the reference operator and anonymous functions.

In fact, a lambda is much like an anonymous function that you can define more easily. As the function doesn’t have a name, it is an anonymous function and it can also be stored in a suitable variable and passed to another function.

You define a lambda by supplying parameters and some code:

{parameter list -> code… }

For example:

{a:Int,b:Int -> a+b}

is a lambda that will add its two parameters together.

Note that a lambda cannot have a return statement – the final value that the lambda computes is automatically returned. In the previous example the lambda automatically returns an Int which is a+b.

A lambda behaves like an expression and you can store a lambda in a variable:

var sum={a:Int,b:Int -> a+b}

You can use the lambda by calling it as a function:

sum(1,2)

which returns 3.

There are a few simplifications of the lambda syntax that can make them look mysterious until you get used to them.

If a lambda has no parameters then you can leave them out and the arrow. So:

{1+2}

is a lambda that returns 3.

At its most extreme a lambda can simply return a value:

{0}

These rules can make lambda expressions look very strange in your code and this might make it harder to read. Don’t go for the shortest and most compact expression make sure your code is easy to understand.

Events Using Lambdas

To define an event handler for an event all you have to do is to use the SetEventListener method with a lambda as its parameter. The lambda is the event handling function.

For example:

button.setOnClickListener(
 {view->(view as Button).text = "You Clicked Me"}
)

sets the lambda:

{view -> (view as Button).text = "You Clicked Me"}

as the event handler. Notice that you don’t have to specify the type of the parameter because the compiler can deduce it from the type of the parameter that the setOnClickListener takes.

There is one last syntactic simplification. If a function accepts a single function as its only parameter you can omit the parentheses:

b.setOnClickListener {
view->(view as Button).text ="You Clicked Me"}

This is the form Android Studio uses for any events it generates in templates. Its only advantage is that it looks more as if the code of the event handler is part of the method it is being defined in.

You can also store the lambda in a variable and use it later, but in this case the compiler cannot work out what the parameter and return types are and so you have to specify them.

For example:

val clickEvent= {view:View ->
       (view as Button).text = "You Clicked Me"}
b.setOnClickListener(clickEvent)

Notice that, as with the other methods, the lambda is converted to an instance of the event object before it is passed to the setOnEventListener method.

Lambdas are used for all Android Studio generated event handlers and it is the standard way of doing the job. Where possible it is the method used in all further examples in this book.

Closure

Closure is one of those topics that sounds as if it is going to be difficult. Lambda expressions are different from most functions in that they have access to all of the variables that belong to the method that they are declared in.

The fact that the lambda has access to the variables in the enclosing method has the strange consequence that you could write the event handler as:

val message="You Clicked Me" button.setOnClickListener {
          view -> button.text = message}

This may look odd but it works. If you don’t think that it is odd then you haven’t noticed that the event handler, the lambda, will be executed when the Button is clicked and this is likely to be well after the enclosing method has finished and all its variables not longer exist – and yet the lambda can still use message to set the Button’s text.

The system will keep the value of message so that the lambda can make use of it. This is the essence of a closure – the preserving of variables that have gone out of scope so that a lambda can still access them.

Unlike Java the variables captured by the Kotlin closure do not have to be final.

Notice that accessing message within the lambda makes it look as if the lambda is naturally still part of the code it is being defined in and not a “detached” functional entity that runs at some time in the distant future – which is what it really is.

Now we come to a subtle point.

The variables captured by a Kotlin closure are shared between all entities that capture them. That is, they are captured as references to the variable. This only matters when more than one lambda is in use.

For example place two Buttons on the design surface and in the onCreate event handler add:

var i=0 button.setOnClickListener {view ->
          button.text = (++i).toString()} button2.setOnClickListener {view ->
          button2.text = (++i).toString()}

Notice that the click event handler for each button captures the variable i within its closure and both share the same variable. If you click the first button you will see 1 as its caption and if you then click the other button you will see 2 as its caption. The lambdas are sharing a single captured variable.

Lambdas are not the only entity that you can use to define a function complete with a closure. Local functions passed by reference, objects that implement SAMs and anonymous functions all have closures that work in the same way. Function references don’t have closure because they are defined away from where they are used.

For example, using a local named function:

var i=0
fun eventHandler(v:View){
           button.text = (++i).toString() } button.setOnClickListener(::eventHandler)

Notice that this doesn’t work for a general named function or method. It has to be a local function.

Similarly for an anonymous function:

var i=0
button.setOnClickListener(
  fun (v:View){ button.text = (++i).toString() })

This works because of the same closure.

For example, using an object:

var i=0
button.setOnClickListener(
  object: View.OnClickListener {
         override fun onClick(p0: View?) {
            button.text = (++i).toString()
         }
  }

)

the function defined in the object has access to i via a closure.

When you first meet the idea of a closure it can seem very strange and even unnecessary. However, when a function is defined within a method the method forms its local context and it is very natural that it should have access to the local variables that are in scope when it is defined.

Closure is useful for event handlers but it is particularly useful when used with callbacks supplied to long running function. The callback usually has to process the result of the long running function and having access to the data that was current when it was created is natural and useful.

There are dangers, however, to relying on a closure. Not everything that was in scope at the time the function was declared can be guaranteed to still exist. If a local variable is set to reference an object then it will be included in the closure and the variable will exist, but there is no guarantee that the object it referenced will still exist at the time that event handler is executed.

Using Breakpoints

Final version in print book

Modern Java Event Handling

Final version in print book

Summary

  • In Java you can't pass a function to set up an event handler you have to pass an object that contains the function as a method.

  • Events are mostly handled by Single Abstract Method SAM interfaces. The method declared in the SAM is the function that does the event handling.

  • For each event and each control that can generate that event there is a SAM of the form onEventListener and the object has a seteventListener method which can be used to attach the event handler.

  • You can create a SAM in a number of ways the most general of which is to use a Kotlin object. This can be used even when the event isn’t implemented as a SAM.

  • There are three alternative ways of implementing a SAM other than using an object:

      1. Function References

      2. Anonymous Functions

      3. Lambda Expressions

  • Function references can be used to pass a package or local level function or a method.

  • An anonymous function works in the same way but you don’t need the reference operator.

  • A lambda is a shorter way of writing an anonymous function and it is the standard way of implementing event handlers.

  • The final syntactic simplification is that if the setListener function has a single function parameter then you can drop the parentheses.

  • Local objects, local function references, anonymous functions and lambda are all subject to closures which make the variables that are accessible to them at the time of their declaration accessible when they are run.

  • Breakpoints are the best way to debug your program and they become essential as soon as you start to implement several event handlers. When run in debug mode a breakpoint will pause your program so that you can examine the contents of variables.

  • If you can restrict your attention to Kotlin you can create event handlers using nothing but objects and lambdas. If you need to understand Java code then there are a number of other ways of doing the job including implementing the interface in the Activity and using anonymous local classes.

 

 

Android Programming In Kotlin
Starting with an App

Covers Android Studio 3 and Constraint Layout.

Is now available as a print book:

coverKotlinsmall

Buy from: Amazon

Contents

  1. Getting Started With Android Studio 3
  2. The Activity And The UI
        Extract: Activity & UI  
  3. Building The UI and a Calculator App
        Extract: A First App
  4. Android Events
  5. Basic Controls
        Extract Basic Controls
        Extract More Controls ***NEW!
  6. Layout Containers
        Extract Layouts - LinearLayout
  7. The ConstraintLayout 
        Extract Bias & Chains
  8. Programming The UI
        Extract Programming the UI
        Extract Layouts and Autonaming Components
  9. Menus & The Action Bar
  10. Menus, Context & Popup
  11. Resources
        Extract Conditional Resources
  12. Beginning Bitmap Graphics
        Extract Animation
  13. Staying Alive! Lifecycle & State
        Extract  State Managment
  14. Spinners
  15. Pickers
  16. ListView And Adapters
  17. Android The Kotlin Way

If you are interested in creating custom template also see:

Custom Projects In Android Studio

Androidgears

 

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

raspberry pi books

 

Comments




or email your comment to: comments@i-programmer.info



Last Updated ( Monday, 14 May 2018 )