Kotlin - Illegal usage of inline parameter callback - android

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.

Related

Overload resolution ambiguity for overloaded higher order function with function-type and generic input

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

Problems with Kotlin Result<T> on unit tests

I am working on an android Application and I opted to use Kotlin Result class so as to handle success/failure on my operations. I made the changes to the code, but the tests stop working and I cannot understand why. Here I show you some snippets:
FireStoreClient.kt
suspend fun items(): Result<ItemsResponse>
NetworkDataSource.kt
suspend fun getItems(): List<Item> =
fireStoreClient.items().fold({ it.items.map { item -> item.toDomain() } }, { emptyList() })
NetworkDataSourceTest.kt
#ExperimentalCoroutinesApi
#Test
fun `Check getItems works properly`() = runBlockingTest {
whenever(fireStoreClient.items()).doReturn(success(MOCK_ITEMS_DOCUMENT))
val expectedResult = listOf(
Item(
id = 1,
desc = "Description 1"
),
Item(
id = 2,
desc = "Description 2"
)
)
assertEquals(expectedResult, dataSource.getItems())
}
And this is the exception I am getting right now. Any clue? It appears that the fold() method is not being executed when unit testing.
java.lang.ClassCastException: kotlin.Result cannot be cast to ItemsResponse
at NetworkDataSource.getItems(NetworkDataSource.kt:31)
I've found a different workaround for this result-wrapping issue, for those who don't want to make their own Result type.
This issue appears to happens specifically when using Mockito's .thenReturn on suspend functions. I've found that using .thenAnswer doesn't exhibit the problem.
So instead of writing this in your unit test (changed doReturn to thenReturn here):
whenever(fireStoreClient.items()).thenReturn(success(MOCK_ITEMS_DOCUMENT))
Use:
whenever(fireStoreClient.items()).thenAnswer { success(MOCK_ITEMS_DOCUMENT) }
Edit: I should note that I was still experiencing this issue when running Kotlin 1.5.0.
Edit: On Kotlin 1.5.20 I can use .thenReturn again.
After a deep dive into the problem, finally, I've found a temporary workaround that works in the testing environment. The problem is, somehow the value of the Result object is wrapped by another Result, and we can pull the desired value or exception using reflection.
So, I've created an extension function called mockSafeFold, which implements the fold behavior in normal calls, and acts fine when you are executing unit-tests.
inline fun <R, reified T> Result<T>.mockSafeFold(
onSuccess: (value: T) -> R,
onFailure: (exception: Throwable) -> R
): R = when {
isSuccess -> {
val value = getOrNull()
try {
onSuccess(value as T)
} catch (e: ClassCastException) {
// This block of code is only executed in testing environment, when we are mocking a
// function that returns a `Result` object.
val valueNotNull = value!!
if ((value as Result<*>).isSuccess) {
valueNotNull::class.java.getDeclaredField("value").let {
it.isAccessible = true
it.get(value) as T
}.let(onSuccess)
} else {
valueNotNull::class.java.getDeclaredField("value").let {
it.isAccessible = true
it.get(value)
}.let { failure ->
failure!!::class.java.getDeclaredField("exception").let {
it.isAccessible = true
it.get(failure) as Exception
}
}.let(onFailure)
}
}
}
else -> onFailure(exceptionOrNull() ?: Exception())
}
Then, simply call it instead of fold:
val result: Result = myUseCase(param)
result.mockSafeFold(
onSuccess = { /* do whatever */ },
onFailure = { /* do whatever */ }
)
I had the same issue.
I noticed that my method of injected class which should return Result<List<Any>> returns actually Result<Result<List<Any>>> which causes the ClassCastException. I used the Evaluate Expression option for the result from the method and I got
Success(Success([]))
The app works well but unit tests didn't pass due this problem.
As a temporary solution I built a new simple implementation of Result sealed class with fold() extension function. It should be easy to replace in future to kotlin.Result
Result sealed class:
sealed class Result<T> {
data class Success<T>(val value: T) : Result<T>()
data class Failure<T>(val error: Throwable) : Result<T>()
}
fold() extension function:
inline fun <R, T> Result<T>.fold(
onSuccess: (value: T) -> R,
onFailure: (exception: Throwable) -> R
): R = when (this) {
is Result.Success -> onSuccess(value)
is Result.Failure -> onFailure(error)
}

pass function with parameters in extension function(kotlin)

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() }

How to pass a function as parameter in kotlin - Android

How to pass a function in android using Kotlin . I can able to pass if i know the function like :
fun a(b :() -> Unit){
}
fun b(){
}
I want to pass any function like ->
fun passAnyFunc(fun : (?) ->Unit){}
You can use anonymous function or a lambda as follows
fun main(args: Array<String>) {
fun something(exec: Boolean, func: () -> Unit) {
if(exec) {
func()
}
}
//Anonymous function
something(true, fun() {
println("bleh")
})
//Lambda
something(true) {
println("bleh")
}
}
Method as parameter Example:
fun main(args: Array<String>) {
// Here passing 2 value of first parameter but second parameter
// We are not passing any value here , just body is here
calculation("value of two number is : ", { a, b -> a * b} );
}
// In the implementation we will received two parameter
// 1. message - message
// 2. lamda method which holding two parameter a and b
fun calculation(message: String, method_as_param: (a:Int, b:Int) -> Int) {
// Here we get method as parameter and require 2 params and add value
// to this two parameter which calculate and return expected value
val result = method_as_param(10, 10);
// print and see the result.
println(message + result)
}
Use an interface:
interface YourInterface {
fun functionToCall(param: String)
}
fun yourFunction(delegate: YourInterface) {
delegate.functionToCall("Hello")
}
yourFunction(object : YourInterface {
override fun functionToCall(param: String) {
// param = hello
}
})
First declare a lambda function(a function without name is known as lamdda function. its comes from kotlin standard lib not kotlin langauage which strat with {} ) in Oncreate like below
var lambda={a:Int,b:Int->a+b}
Now Create a function which accept another function as a parameter like below
fun Addition(c:Int, lambda:(Int, Int)-> Int){
var result = c+lambda(10,25)
println(result)
}
Now Call Addition function in onCreate by passing lambda as parameter like below
Addition(10,lambda)// output 45

kotlin: call higher-order function with multiple lambdas

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.

Categories

Resources