Is it possible to call a function with multiple lambda functions?
If so, how can I invoke the following function?
fun post(path: String,
params: JSONObject,
completionHandler: (response: JSONObject?) -> Unit,
errorCompletionHandler: (error: VolleyError?) -> Unit
)
Yes, you can have as many lambdas as you like. The shown post can be invoked as follows:
post("/a", "json", {response-> println(response) }, { error-> println(error)})
It's also possible to lift the last lambda out of the parentheses as described in the documentation:
In Kotlin, there is a convention that if the last parameter to a function is a function, and you're passing a lambda expression as the corresponding argument, you can specify it outside of parentheses.
Applied to your code, this means:
post("/a", "json", { response -> println(response) }) { error ->
println(error)
}
You would define the lambdas as shown below. You can assign them to variables to make the code more readable. This becomes especially handy if you lambda become bigger.
val completionHandler: (JSONObject?) -> Unit = { response ->
// ...
}
val errorCompletionHandler: (VolleyError?) -> Unit = { error ->
// ...
}
post("/path", jsonObject, completionHandler, errorCompletionHandler)
Or you can define functions which you pass using a reference:
fun errorCompletionHandler(error: VolleyError?) {}
fun completionHandler(response: JSONObject?) {}
post("/path", jsonObject, ::completionHandler, ::errorCompletionHandler)
Notice that Unit can be ommitted here because it is the implicit return type if nothing else was specified.
Related
I have these two overloads for a higher order function:
// first function
fun<T> Response<T>.onSuccess(predicate: (T) -> Unit) {
val body = body()
if (isSuccessful && body != null) {
predicate(body)
}
}
// second function
fun<T> Response<T>.onSuccess(predicate: () -> Unit) {
if (isSuccessful)
predicate()
}
The first one has a function-type which takes a generic type argument and returns Unit. While the second function has a function-type that doesn't take any arguments.
The Response is the retrofit2.Response object which takes a generic type.
I can call the first function by explicitly naming the lamda parameter as follows:
response.onSuccess { result ->
// not implemented
}
But the compiler gives a overload resolution ambiguity error when I try to call the second function:
response.onSuccess { // error
// not implemented
}
Overload resolution ambiguity. All these functions match.
public fun Response<TypeVariable(T)>.onSuccess(predicate: () → Unit): Unit defined in com.vs.data.network in file NetworkHelper.kt
public fun Response<TypeVariable(T)>.onSuccess(predicate: (TypeVariable(T)) →
Unit): Unit defined in com.vs.data.network in file NetworkHelper.kt
I am trying to create an Extension function in android with Handlers but facing issue:
Extension fun Code:
fun delayTask(millis: Long, myFunction: (data:String) -> Unit) {
Handler().postDelayed({
myFunction(data) //why error is of data here
}, millis)
}
Calling like this:
delayTask(500, ::function)
Getting error Unresolved reference: data
data is not a parameter of your higher order function. It is a parameter of your function parameter. So it doesn't exist for you to pass to the passed function.
To be able to pass this data to your lambda, you will need to add it as another parameter:
fun delayTask(millis: Long, data: String, myFunction: (String) -> Unit) {
Handler().postDelayed({
myFunction(data)
}, millis)
}
And when you call it, you would have to also pass the data:
delayTask(500, someDataString, ::function)
Your function could be more versatile by removing the parameter from the function parameter. Then you could call any function with any amount of parameters needed just by wrapping it in a lambda:
fun delayTask(millis: Long, myFunction: () -> Unit) {
Handler().postDelayed({
myFunction()
}, millis)
}
delayTask(500) { myFunction(someData) }
For performance reasons, it would be better to make it inline. But the passed function has to be crossinline since it's wrapped in another object and called later:
inline fun delayTask(millis: Long, crossinline myFunction: () -> Unit) {
Handler().postDelayed({
myFunction()
}, millis)
}
Note this functionality is already available with the postDelayed function in Android Ktx core:
Handler().postDelayed(500L) { someFunction() }
Can we pass two higher order functions or more than one, in function's parameter?
If so, then how we going to call that function which contain those two or more than one higher order functions as a parameter...
Of couse you can. You can have any number of lambda parameters just like for parameters of any other type.
Even Kotlins standard library makes use of that. For example the generateSequence function.
generateSequence(seedFunction = { 1 }, nextFunction = { it + 1 })
The only difference to a non-lambda parameter is that you can omit the parentheses if the last parameter of your function is a lambda. So you could call generateSequence like this too:
generateSequence(seedFunction = { 1 }) { it + 1 }
Wording
There might be a misunderstanding of what a Higher-Order function is:
GeeksForGeeks:
In Kotlin, a function which can accepts a function as parameter or can
returns a function is called Higher-Order function.
And now the definition for functions:
Kotlinlang:
Kotlin functions are first-class, which means that they can be stored
in variables and data structures, passed as arguments to and returned
from other higher-order functions. You can operate with functions in
any way that is possible for other non-function values.
Conclusion
Functions can be passed as parameters into methods - just as many as you like. High-order is just a (descriptive) type, which means that your method either takes functions as parameters, or returns a function.
Example:
fun <T> takeFiveFunctions(
block1 : () -> Unit,
block2 : (T) -> Unit,
block3 : () -> T,
block4 : (T) -> T,
block5 : (List<T>) -> T
) : Boolean = true
fun main() {
takeFiveFunctions<Int>(
block1 = { /*do something 1*/ },
block2 = { print(it) },
block3 = { 2 },
block4 = { it * 3 },
block5 = { it.first() }
)
}
EDIT
but some other syntax says we can take out lambda from parentheses
Kotlinlang says:
In Kotlin, there is a 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:
Using my previous example, it would look like this:
fun main() {
takeFiveFunctions<Int>(
block1 = { /*do something 1*/ },
block2 = { print(it) },
block3 = { 2 },
block4 = { it * 3 }
) { it.first()}
}
Yes, why not. You can just do that:
fun <T1, T2, T3> Collection<T1>.higherOrder(
firstFun: (T1)->T2,
secondFun: (T2)->T3)
: List<T3> {
return this.map(firstFun).map(secondFun)
}
Invocation:
val result = listOf(3.14, 6.9, 42.0).higherOrder({ it.toInt() }, { it.toString() })
// ["3", "6", "42"]
Kotlin allows to move last lambda parameter outside parameter list, so you can also write:
val result = listOf(3.14, 6.9, 42.0).higherOrder({ it.toInt() }) { it.toString() }
But in case of multiple functional parameters, that's bad practice. The best you can do is to specify parameter names explicitly:
val result = listOf(3.14, 6.9, 42.0).higherOrder(
firstFun = { it.toInt() },
secondFun = { it.toString() }
)
Which is much more readable.
I am new to kotlin and I am a little confused while using lambda expression in LiveData observe method.
The signature for observe method is as follows
observe(LifecycleOwner owner, Observer<? super T> observer)
where Observer is an interface with a single method
void onChanged (T t)
However,calling the above observe method in kotlin as follows gives type mismatch error :
val myViewModel = ViewModelProviders.of(this).get(AnimeListViewModel::class.java)
myViewModel.animes.observe(this, { anime -> println(anime) })
Isn't this the same as calling the setOnClickListener on a view. The following piece of code works perfectly without any compilation error:
val myView = View(this)
myView.setOnClickListener { view -> println(view) }
I have already read this answer which shows how to call the method using lambda expression (using SAM conversion). However, I am still not sure why a simple arrow expression would fail.
LiveData doesn't have a lambda expression, you should pass the observer interface as an object
myViewModel.animes.observe(this, Observer { anime -> println(anime) })
Or by creating an extension function like this
fun <T : Any> LiveData<T>.observe(lifecycleOwner: LifecycleOwner, block: (T) -> Unit) = observe(lifecycleOwner, Observer(block))
And calling it like this
myViewModel.animes.observe(this) { anime -> println(anime) }
Or like this
fun main() {
myViewModel.animes.observe(this, ::handleLiveData)
}
fun handleLiveData(anime: Anime) {
println(anime)
}
There are some problems on kotlin to resolve generics, so that's the reason. Kotlin has been working on this, and you will find the whole explanation
here.
I'm converting my function having lambda as parameter into inline function for performance improvement.
I have list of lambda of type MutableList<(Authenticate) -> Unit> variable as data member in class. When I try to adding lambda parameter into the list.
Kotlin compiler says:
Illegal usage of inline parameter callback
Here is the code
// Some code skipped
object Odoo {
val pendingAuthenticateCallbacks = mutableListOf<(Authenticate) -> Unit>()
inline fun authenticate(
login: String, password: String, database: String,
quick: Boolean = false, crossinline callback: Authenticate.() -> Unit
) {
// Following statement has error saying
// Illegal usage of inline parameter callback. add 'noinline' modifier to parameter declaration.
pendingAuthenticateCallbacks += callback
// Error in above statement
if (pendingAuthenticateCallbacks.size == 1) {
// Retrofit2 Object boxing code skipped
val call = request.authenticate(requestBody)
call.enqueue(object : Callback<Authenticate> {
override fun onFailure(call: Call<Authenticate>, t: Throwable) {
(pendingAuthenticateCallbacks.size - 1 downTo 0)
.map { pendingAuthenticateCallbacks.removeAt(it) }
.forEach {
it(Authenticate(httpError = HttpError(
Int.MAX_VALUE,
t.message!!
)))
}
}
override fun onResponse(call: Call<Authenticate>, response: Response<Authenticate>) {
(pendingAuthenticateCallbacks.size - 1 downTo 0)
.map { pendingAuthenticateCallbacks.removeAt(it) }
.forEach {
it(Authenticate(httpError = HttpError(
response.code(),
response.errorBody()!!.string()
)))
}
}
})
}
}
}
Inlining inserts the code in the lambda directly into the call site, which removes the overhead of having a function object.
For example, this roughly results in main here:
fun withLambda(lambda: () -> Unit) {
lambda()
}
inline fun inlinedLambda(lambda: () -> Unit) {
lambda()
}
fun main(args: Array<String>) {
withLambda { println("Hello, world") }
inlinedLambda { println("Hello, world") }
}
being converted to this:
fun main(args: Array<String>) {
withLambda { println("Hello, world") }
println("Hello, world") // <- Directly inserted!
}
If you have
pendingAuthenticateCallbacks += callback
This is impossible because callback must be an object in order for it to be added to the list.
You need to add the noinline modifier.
A rough approximation would be to say that an inlined lambda cannot be treated as an object, as it doesn't really exist as an object. It is used directly instead of being created as an object.
Of course, you could create a containing lambda:
pendingAuthenticateCallbacks += { callback() } // Not a good idea
but this would entirely defeat the point of inlining (don't do this!).
However, making the parameter noinline would mean your method now has zero lambda parameters that can be inlined, so you might as well just remove the inline modifier as performance benefit would be minimal.
The compiler should recognize this:
Note that if an inline function has no inlinable function parameters and no reified type parameters, the compiler will issue a warning, since inlining such functions is very unlikely to be beneficial.
The main reason for inlining methods is for performance when using lambdas and for reified generic type parameters. As of Kotlin 1.1, it is also possible to have an inline property accessor for properties without a backing field.
In short, if you have no lambda parameters (or no reified type parameters, in which case you must), it is usually pointless to mark a function as inline.