Kotlin setOnclickListener - android

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.

Related

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

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.

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.

Kotlin - How to skip entries on exceptions when mapping and filtering

I wonder how to skip one entry while mapping or filtering?
fun getFilteredList(list: List<String>, match: String): List<String>? {
val flist = list.filter {
try {
// Some parsing logic which can throw exceptions
val res = SomeParser.parse(match)
it == res
} catch (e: Exception){
// I want to skip this entry to include in flist but still continue
// for left of the list entries without return. How to do that?
return null
}
}
return flist
}
The problem is your use of the return keyword. Your code should look like:
// return type no longer needs to be nullable
fun getFilteredList(list: List<String>, match: String): List<String> {
val flist = list.filter {
try {
val res = SomeParser.parse(match)
it == res
} catch (e: Exception){
true
}
}
return flist
}
Or, based on your example, just (using "single-expression function" syntax):
fun getFilteredList(list: List<String>, match: String) = list.filter {
try {
it == SomeParser.parse(match)
} catch (ex: Exception) {
true
}
}
Note: Both of these will call SomeParser.parse for each element in the list. However, you mention this is just an example and the real code works differently (i.e. can't be pulled out of the filter operation).
The reason return was giving you an error has to do with lambda expressions. From the Returning a value from a lambda expression section of the Kotlin reference:
We can explicitly return a value from the lambda using the qualified return syntax. Otherwise, the value of the last expression is implicitly returned.
Therefore, the two following snippets are equivalent:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return#filter shouldFilter
}
[...]
And this is in combination with the fact filter is an inline function. From the Non-local retuns section of the Kotlin reference:
In Kotlin, we can only use a normal, unqualified return to exit a named function or an anonymous function. This means that to exit a lambda, we have to use a label, and a bare return is forbidden inside a lambda, because a lambda cannot make the enclosing function return:
fun foo() {
ordinaryFunction {
return // ERROR: cannot make `foo` return here
}
}
But if the function the lambda is passed to is inlined, the return can be inlined as well, so it is allowed:
fun foo() {
inlined {
return // OK: the lambda is inlined
}
}
Such returns (located in a lambda, but exiting the enclosing function) are called non-local returns. [...]
This means when you had return null it was actually trying to exit the entire getFilteredList function. I'm assuming this is why you have your return type as List<String>? instead of List<String>. And when you tried return false you were trying to return a Boolean from a function whose return type was List<String>?.

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

Categories

Resources