How to understand when I can use a Kotlin anonymous function? - android

I'm struggling with Kotlin anonymous functions. The syntax seems to vary (sometimes parenthesis are used, sometimes not) and I'm not sure where they can vs. cannot be used.
In this code for example, what exactly is the anonymous function replacing? It just seems to suddenly pop up in the middle of a builder and it's not at all clear what it's doing.
// Choose between gallery and camera
val builder = AlertDialog.Builder(this)
val sources = arrayOf("Use Camera", "View Gallery")
builder.setItems(sources) { _, which ->
when (which) {
0 -> { cameraIntentLauncher.launch(cameraIntent) }
1 -> { galleryIntentLauncher.launch(galleryIntent) }
}
}.create().show().
Is it associated with setItems()? Not really. It appears to be defining the callback function that occurs after an item is selected. But there's no ".setCallback()" to attach it to the builder.
In regular Java, I'd expect something like:
builder.setItems(sources, new callbackFunc {...})
But in the above Kotlin, the anonymous function is defined outside of the setItems() parenthesis. It's very confusing.
So what can I read or watch to understand this Kotlin syntax implicitly when reading it?

The concept used here is called Trailing lambdas.
According to Kotlin convention, if the last parameter of a function is
a function, then a lambda expression passed as the corresponding
argument can be placed outside the parentheses
So your code,
builder
.setItems(sources) { _, which ->
when (which) {
0 -> {
cameraIntentLauncher.launch(cameraIntent)
}
1 -> {
galleryIntentLauncher.launch(galleryIntent)
}
}
}
.create()
.show()
And the below code would be considered as same.
builder
.setItems(sources, { _, which ->
when (which) {
0 -> {
cameraIntentLauncher.launch(cameraIntent)
}
1 -> {
galleryIntentLauncher.launch(galleryIntent)
}
}
})
.create()
.show()
Adding more details as suggested in the comments.
If the lambda is the only argument in that call, the parentheses can be omitted entirely.
Example
run { println("...") }
Lambdas and anonymous functions are not the same.
Difference in return type
The return type inference for anonymous functions works just like for normal functions: the return type is inferred automatically for anonymous functions with an expression body, but it has to be specified explicitly (or is assumed to be Unit) for anonymous functions with a block body.
Difference in return statement
A difference between lambda expressions and anonymous functions is the behavior of non-local returns.
A return statement without a label always returns from the function declared with the fun keyword.
This means that a return inside a lambda expression will return from the enclosing function, whereas a return inside an anonymous function will return from the anonymous function itself.
When passing anonymous functions as parameters, place them inside the parentheses. The shorthand syntax that allows you to leave the function outside the parentheses works only for lambda expressions.

Related

return#methodName - Meaning of that syntax?

This code is from an Udemy-course I'm currently doing:
if (noteTitle.isNullOrEmpty()) {
title_et.error = "Title required"
return#setOnClickListener
}
What it is basically doing is clear to me. But what's that 'return#setOnClickListener' statement?
What's the meaning of these #method-name syntax? What means the #-character here?
Its qualified returns.
Consider the following code:
fun foo(strings: List<String?>) {
strings.forEach {
if (it.isEmptyOrNull()) return
println(it)
}
}
Since forEach is an inline function, return statement will return from the parent function foo instead.
To simply return from one iteration of forEach you qualify to return only on a particular scope:
strings.forEach {
if (it.isEmptyOrNull()) return#forEach
// ...
}
You can customize it with other name if they are ambiguous:
strings.forEach outer# { a ->
list2.forEach inner# { b ->
if (a.isEmptyOrNull()) return#outer
// ...
}
}
This is useful mostly in inline and cross-inline situations, but sometimes you also want to explicitly qualify in non-inline lambdas (just to be more precise) as in the example you mentioned.

"return is not allowed here" when returning inside a kotlin lambda

I use a lambda to handle callbacks from an asynchronous call. I'd like to define the callback outside the calling method to avoid a bulky method, but I can't seem to use early returns within the lambda, which makes the code unnecessarily difficult to read.
I've tried defining the lambda as a variable but returns are not viable inside the lambda.
I've tried defining the lambda inside a function and returning but returns were not viable there either.
For example:
private fun onDataUpdated(): (Resource<List<Int>>) -> Unit = {
if (it.data.isNullOrEmpty()) {
// Handle no data callback and return early.
return#onDataUpdated // This is not allowed
}
// Handle the data update
}
}
I've also tried:
private val onDataUpdated: (Resource<List<Int>>) -> Unit = {
if (it.data.isNullOrEmpty()) {
// Handle no data callback and return early.
return // This is not allowed
}
// Handle the data update
}
}
I'd like to perform an early return instead of using an else case, to avoid unnecessary indent, but I can't seem to find a way to use returns inside a lambda.
You can achieve this by labelling the lambda. For example, if you label it with dataUpdated:
private val onDataUpdated: (Resource<List<Int>>) -> Unit = dataUpdated# {
if (it.data.isNullOrEmpty()) {
// Handle no data callback and return early.
return#dataUpdated
}
// Handle the data update
}
Usually you'll just put the "return" at the bottom of the lambda. Here's an example using Dispatchers.IO:
var res = async(Dispatchers.IO){
var text : String? = null
if(channel==null) {
text = networkRequest(url)
}
text
}.await()

Kotlin about passing lambda as property

I am confusing about merit of writing code as following 2 case:
class TestA {
private val foo: Boolean by lazy {
// Here is logic that return true or false
}
Case 1:
fun main() {
TestB({foo})
}
Case 2:
fun main() {
TestB(foo)
}
}
Case 1:
class TestB(private val isFoo: () -> Boolean ) {
fun checkFoo(): Boolean {
return isFoo.invoke()
}
}
Case 2:
class TestB(private val isFoo: Boolean ) {
fun checkFoo(): Boolean {
return isFoo
}
}
When should I use case 1 or case 2 ?
By the way, please let me know how does invoke() method work?
You only pass lambdas into other class constructors if you want something to be invoked on the other end, that might make sense if used as a callback, or if you need to have a function that creates objects again and again rather than being static. In this case, you'd store the lambda for later referral and invoke it whenever needed. If you just pass a static instance around, that is for example foo in your code, there's no reason for a lambda. You should always prefer not to use lambdas for constructors; scenarios in which they are useful or necessary are rather rare IMO.
As to your question regarding invoke: Kotlin has a number of functions that work "by convention", e.g. rangeTo, equals, contains, compareTo, the index operators and also invoke. Learn about conventions here.
Now, whenever a class provides the invoke operator, you can call instances of that class as if they were functions:
class InvokeMe(){
operator fun invoke(value: Int) = println("invoked with $value")
}
val obj = InvokeMe()
//both are compiled to the same code
obj(10)
obj.invoke(5)
Since every lambda is being compiled into a Function instance (see kotlin.jvm.functions) which comes with an implementation of invoke, you can call lambdas as shown above, i.e., using lambda(args) or lambda.invoke(args)
.invoke() will simply call your lambda and give your result, same as calling a function.
As for when you should pass a lambda or an actual value, it very depends.
Personally, I would only suggest using lambdas in very specific situations, overusing them can make your code very confusing and hard to refactor. If you just want a result to be passed into the function, just pass the actual value. Don't make someone else call .invoke().
But a few good example for a lambda are callsbacks, or onClickListeners.
// A login network request with a lambda handling the result.
fun login( username: String, password: String, onResult: (Result) -> Unit ) {
// do some network call, and return a Result.
}
// note: if the last param is a lambda, you can simply move it outside the function call like this.
login( username, password ) { result ->
// use the result of the network request.
}
Hopefully that helps.

Kotlin setOnclickListener

Back in java I used to write only return for a void method... But kotlin doesn't seem to allow just return, instead it uses return#methodname?
Can someone explain what this is and how does it add value?
bAddLine.setOnClickListener {
val selectedSeries = getSelectedSeries()
if (selectedSeries.isEmpty()) {
Toast.makeText(this, getString(R.string.toast_channel_mandatory), Toast.LENGTH_LONG).show()
return#setOnClickListener
}
}
From kotlinlang website:
Return at Labels
With function literals, local functions and object expression, functions can be nested in Kotlin. Qualified returns allow us to return from an outer function. The most important use case is returning from a lambda expression. Recall that when we write this:
fun foo() {
ints.forEach {
if (it == 0) return // nonlocal return from inside lambda directly to the caller of foo()
print(it)
}
}
The return-expression returns from the nearest enclosing function, i.e. foo. (Note that such non-local returns are supported only for lambda expressions passed to inline functions.) If we need to return from a lambda expression, we have to label it and qualify the return:
fun foo() {
ints.forEach lit# {
if (it == 0) return#lit
print(it)
}
}
Now, it returns only from the lambda expression. Oftentimes it is more convenient to use implicits labels: such a label has the same name as the function to which the lambda is passed.
fun foo() {
ints.forEach {
if (it == 0) return#forEach
print(it)
}
}
When inside a lambda, you have to specify which scope you wish to return from, because it might be ambiguous. See the official docs about returning at labels.
In this specific case, if you'd be returning at the end of a function that returns nothing, you can omit the return statement altogether.

Kotlin Android Button.onClickListener causing NoSuchMethodError

I think I've found a quirk of using kotlin for android, or there's some gap in my understanding of the syntax.
Trying to set an onClickListener for a button is throwing a NoSuchMethodError
Here's the code at fault
button.setOnClickListener(Button.OnClickListener {
fun onClick(view: View){
val intent : Intent = Intent(this,DetailActivity::class.java)
if(obj is String) {
intent.putExtra("Topic", obj)
}
startActivity(intent)
}
})
And here's the stacktrace outputted
java.lang.NoSuchMethodError: No static method OnClickListener(Lkotlin/jvm/functions/Function1;)Landroid/view/View$OnClickListener; in class Landroid/widget/Button; or its super classes (declaration of 'android.widget.Button' appears in /system/framework/framework.jar:classes2.dex)
Anyone know whats up?
Interestingly, I don't get that error, your code compiles for me. However, it won't work for a different reason: you're passing in a lambda as the listener inside the {}, which means that the contents of it will be executed when the click event happens. There is no code to run inside it though, you're just defining a local function named onClick that will never be called.
button.setOnClickListener(Button.OnClickListener {
fun onClick(view: View){
...
}
Log.d("TAG", "hi") // this is the code that would be executed on click events
})
There are two ways to fix your syntax:
First, you can use an object expression to create the listener, this is pretty close to what you wrote, and is along the lines of the classic Java solution, it explicitly creates an anonymous class (note that the OnClickListener interface is actually under the View class):
button.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
val intent = ...
}
})
Or you can use the shorter, more Kotlin-like syntax that the IDE will suggest when you try using the previous long form anyway, by making use of SAM conversion:
button.setOnClickListener {
val intent = ...
}
This solution uses a lambda just like your initial code did, it just doesn't name what interface it converts to explicitly, and drops the () which are not required a single lambda parameter.
try
button.setOnClickListener {
// Handler code here
}
You can try :
// case 1
button?.setOnClickListener { view ->
// handler here
}
// case 2
button?.setOnClickListener {
// you can use keyword 'it' for use member view
// handler here
}

Categories

Resources