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

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
}

Related

How to create an extension function with multiple receivers in Kotlin?

I want my extension function to have a couple of receivers. For example, I want function handle to be able to call methods of both CoroutineScope and Iterable instances:
fun handle() {
// I want to call CoroutineScope.launch() and Iterable.map() functions here
map {
launch { /* ... */ }
}
}
I thought this might work:
fun <T> (Iterable<T>, CoroutineScope).handle() {}
But it gives me an error:
Function declaration must have a name
I know that I can create the function with parameters, but
Is it possible to have multiple receivers for a single function and how to do that without parameters?
In the Kotlin version 1.6.20 there is a new feature called Context receivers. This is a first prototype of context receivers. This feature allows to make functions, properties and classes context-dependent by adding context receivers to their declaration. There is a new syntax for that. In front of the function declaration we can specify a list of contextual types that would be required to invoke this function. A contextual declaration does the following:
It requires all declared context receivers to be present in a caller's scope as implicit receivers.
It brings declared context receivers into the body scope of implicit receivers.
The solution with context receivers looks like the following:
context(CoroutineScope)
fun <T> Iterable<T>.handle() {
map {
launch { /* ... */ }
}
}
someCoroutineScope.launch {
val students = listOf(...)
students.handle()
}
In the context(CoroutineScope) we can declare multiple types, e.g context(CoroutineScope, LogInterface).
Since context receivers feature is a prototype, to enable it add -Xcontext-receivers compiler option in the app's build.gradle file:
apply plugin: 'kotlin-android'
android {
//...
kotlinOptions {
jvmTarget = "11"
freeCompilerArgs += [
"-Xcontext-receivers"
]
}
}
As far as I know, this is currently impossible for types that we don't control. There are plans to add such feature, it is processed under KEEP-259.
I don't know what is the planned roadmap or when we could expect it to be added, but I hope we will see at least some previews this year.
This is a very narrow case, but if your use case is that you have a higher order function where you want code in the lambda to have multiple receivers, and if the types you're wanting to combine are interfaces, you can create a class that wraps the interfaces as delegates. Within the lambda passed to the below function, you can call both Iterable and CoroutineScope functions.
class CoroutineScopeAndIterable<T>(
private val coroutineScope: CoroutineScope,
private val iterable: Iterable<T>
): CoroutineScope by coroutineScope, Iterable<T> by iterable
suspend fun <T> CoroutineScope.runSomething(
iterable: Iterable<T>,
block: suspend CoroutineScopeAndIterable<T>.() -> Unit
) {
CoroutineScopeAndIterable(this, iterable).block()
}
Here is workaround you can use:
val <T> Iterable<T>.handle: CoroutineScope.() -> Unit get() = {
map {
launch { }
}
}

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)

What is the purpose of kotlin contract

Was reading the apply function code source and found
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
and contract has an empty body, experimental
#ContractsDsl
#ExperimentalContracts
#InlineOnly
#SinceKotlin("1.3")
#Suppress("UNUSED_PARAMETER")
public inline fun contract(builder: ContractBuilder.() -> Unit) { }
what is the real purpose of contract and is it here to stay in the next versions?
What is the real purpose of contract
The real purpose of Kotlin contracts is to help the compiler to make some assumptions which can't be made by itself. Sometimes the developer knows more than the compiler about the usage of a certain feature and that particular usage can be taught to the compiler.
I'll make an example with callsInPlace since you mentioned it.
Imagine to have the following function:
fun executeOnce(block: () -> Unit) {
block()
}
And invoke it in this way:
fun caller() {
val value: String
executeOnce {
// It doesn't compile since the compiler doesn't know that the lambda
// will be executed once and the reassignment of a val is forbidden.
value = "dummy-string"
}
}
Here Kotlin contracts come in help. You can use callsInPlace to teach the compiler about how many times that lambda will be invoked.
#OptIn(ExperimentalContracts::class)
fun executeOnce(block: ()-> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
#OptIn(ExperimentalContracts::class)
fun caller() {
val value: String
executeOnce {
// Compiles since the val will be assigned once.
value = "dummy-string"
}
}
is it here to stay in the next versions?
Who knows. They are still experimental after one year, which is normal for a major feature. You can't be 100% sure they will be out of experimental, but since they are useful and they are here since one year, in my opinion, likely they'll go out of experimental.

Using Mockk to mock private function that takes a lambda

I am trying to write a unit test for a implementation of an abstract class I wrote. The method I'm trying to mock takes a lambda as it's only parameter. I'm trying to capture this lambda, so I can invoke it and get the result.
This is the method I'm trying to mock:
protected fun update(block: suspend S.() -> S?): Unit
I am using an extension function in my tests like this:
suspend inline fun <reified T : Model<S>, S : State> T.blah(
state: S,
block: (T) -> Unit
): S? {
val spy = spyk(this, recordPrivateCalls = true)
val slot = slot<suspend S.() -> S?>()
every { spy["update"](capture(slot)) } answers { Unit }
block(spy)
return slot.captured.invoke(state)
}
So I am creating a spy, then a slot, then when the update function is called, capture it so that it blocks the actual class from performing the call. Then I invoke the lambda myself and return the value.
However I keep getting this error:
io.mockk.MockKException: can't find function update(kotlin.jvm.functions.Function2$Subclass1#6bfa228c) for dynamic call
at io.mockk.InternalPlatformDsl.dynamicCall(InternalPlatformDsl.kt:122)
at io.mockk.MockKMatcherScope$DynamicCall.invoke(API.kt:1969)
I followed the stacktrace and set a breakpoint in the InternalPlatformDsl.kt class, and traced it to this block of code:
for ((idx, param) in it.parameters.withIndex()) {
val classifier = param.type.classifier
val matches = when (classifier) {
is KClass<*> -> classifier.isInstance(params[idx])
is KTypeParameter -> classifier.upperBounds.anyIsInstance(params[idx])
else -> false
}
if (!matches) {
return#firstOrNull false
}
}
It successfully matches the first parameter which is the class under test Model in this case, but it fails matching the second parameter because it is wrapped in the capture function.
Any ideas on how I can intercept this update call?
I'm using the latest version of mockk, and JUnit 4

How to improve kotlin lambda syntax?

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)

Categories

Resources