|Android Programming In Kotlin: Events|
|Written by Mike James|
|Monday, 14 May 2018|
Page 2 of 2
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:
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.
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:
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
annotation. Notice also that Kotlin lets you call the top-level or package level function without having to give the class name.
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:
then you can write:
If the function is defined as a method within a class you have to write:
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.
An anonymous function is exactly what its name suggests – a function with no name defined. You simply write:
You can store a reference to an anonymous function in a variable of the correct type and pass it to another function. For example:
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.
It is difficult to see any advantage of doing this, however, other than if you are using the same function more than once.
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:
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:
You can use the lambda by calling it as a function:
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:
is a lambda that returns 3.
At its most extreme a lambda can simply return a value:
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.
sets the lambda:
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:
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.
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 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:
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:
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:
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:
This works because of the same closure.
For example, using an object:
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.
Final version in print book
Modern Java Event Handling
Final version in print book
Android Programming In Kotlin
|Last Updated ( Monday, 14 May 2018 )|