How to improve kotlin lambda syntax? - android

I'm trying to use kotlin M12 in android project and during the work I got this piece of code subscribe({onSuccess(it)}, {onFailure(it)})
AppObservable.bindActivity(this, api.get(id)).subscribe({onSuccess(it)}, {onFailure(it)})
fun onSuccess(str: String) {}
fun onFailure(tr: Throwable) {}
This is not so bad, but I think it would/should be better. How can I improve it?

First, create a helper extension method like this:
fun<T, R> Observable<T>.subscribe(
receiver: R,
onSuccess: R.(T) -> Unit,
onFailure: R.(Throwable) -> Unit) {
subscribe({ receiver.onSuccess(it) }, { receiver.onFailure(it) })
}
Now you can use your new method like this:
AppObservable.bindActivity(this, api.get(id)).subscribe(this, ::onSuccess, ::onFailure)
The :: operator creates a method reference. Once KT-6947 gets resolved, we can omit the helper method and write subscribe(this::onSuccess, this::onFailure) directly. This is already possible in Java 8.
To work around this issue we can alternatively implement the following helper method that binds a receiver to an extension method:
fun <T, R, E> T.bind(reference: T.(R) -> E): (R) -> E = { this.reference(it) }
and use it like this:
AppObservable.bindActivity(this, api.get(id)).subscribe(bind(Foo::onSuccess), bind(Foo::onFailure))
but this is hardly more elegant than your initial solution.
EDIT1:
Thy syntax ::onSuccess is forbidden for now, so you have to use 'Foo::onSuccess' where Foo is your class name.
EDIT2:
You can have a little fun with operator overloading by declaring the same method as
operator fun <T, R, E> T.plus(reference: T.(R) -> E): (R) -> E = { this.reference(it) }
Then you can use it like so:
AppObservable.bindActivity(this, api.get(id)).subscribe(this + Foo::onSuccess, this + Foo::onFailure)

Related

How to make an inline function remove itself when its being used as a listener in kotlin

I have hit a wall with this one and I can't find any question with a solution for this here in SO.
I am using a PagingAdapter method, from Google's Paging library, that receives an inline function as a listener:
fun addLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
differ.addLoadStateListener(listener)
}
And then to remove the listener they provide the following method
fun removeLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
differ.removeLoadStateListener(listener)
}
And I am using it like this
myPagingAdapter.addLoadStateListener { it: CombinedLoadStates ->
myPagingAdapter.removeLoadStateListener(this)
}
I know the above does not work, but it worked when the file was written in java since it had a correct reference to itself inside its own function. However, in Kotlin I cannot find a way to do this at all. I tried turning into an anonymous function, but it still won't pass the correct context
myPagingAdapter.addLoadStateListener { fun(it: CombinedLoadStates) ->
myPagingAdapter.removeLoadStateListener(this)
}
At this point I have no idea how I can remove an inline function that can't reference itself, and I cannot find any documentation with a solution for this anywhere.
How can I remove in kotlin an inline function by referencing itself?
If I understand correctly, you need a reference of inline function which was passed in addLoadStateListener so you can pass in removeLoadStateListener.
You can try this
myPagingAdapter.addLoadStateListener(object : (String) -> Unit {
override fun invoke(p1: String) {
myPagingAdapter.removeLoadStateListener(this)
}
})
You can create a local function to reference itself:
fun myFun(CombinedLoadStates): Unit {
myPagingAdapter.removeLoadStateListener(::myFun)
}
myPagingAdapter.addLoadStateListener(::myFun)

Unable to use lambda arrow expression for Livedata observe method in Kotlin

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.

How to declare an interface that works with lambda? [duplicate]

This question already has answers here:
Lambda implementation of interface in kotlin
(5 answers)
Closed 4 years ago.
I declared a simple interface like this:
interface OnSomethingReadyListener {
fun onSomethingReady()
}
And of course a setter:
private val onSomethingReadyListeners = ArrayList<OnSomethingReadyListener>()
fun addOnSomethingReadyListener(callback: OnSomethingReadyListener) {
onSomethingReadyListeners.add(callback)
}
But then I found that I cannot use lambda:
something.addOnShopDataReadyListener { progressbar.visibility = View.GONE }
IDE gave me an error:
Type mismatch.
Required: SomeClass.OnSomethingReadyListener
Found: () -> Unit
Suppose I want to stick to using lambda instead of anonymous class (object : OnSomethingReadyListener {...}). How should I declare OnSomethingReadyListener?
SAM (single abstract method) conversion is only support for Java interfaces, not Kotlin interfaces.
The documentation states this reasoning:
Also note that this feature works only for Java interop; since Kotlin has proper function types, automatic conversion of functions into implementations of Kotlin interfaces is unnecessary and therefore unsupported.
KT-7770 requests this functionality, if you want to track whether it is being considered or not.
The recommended way to achieve what you want is to replace the OnSomethingReadyListener with a parameter of type () -> Unit like so:
private val onSomethingReadyListeners = ArrayList<() -> Unit>()
fun addOnSomethingReadyListener(callback: () -> Unit) {
onSomethingReadyListeners.add(callback)
}
You can then invoke those listeners with something like
onSomethingReadyListeners.forEach { it.invoke() }
You have to declare the function as this:
fun addOnSomethingReadyListener(block: (OnSomethingReadyListener) -> Unit)
And then you can call it as follow:
addOnSomethingReadyListener {
// TODO
}

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.

Kotlin - Illegal usage of inline parameter callback

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.

Categories

Resources