I need to pass a coroutine function as a parameter for another function. For example:
private fun handleIt(here: Long, call: (hereId: Long) -> Unit) {
call(here)
}
and then from the coroutine scope:
GlobalScope.launch {
handleIt(3) { viewModel.doThings(hereId) }
}
The viewModel function looks like this:
suspend fun doThings(hereId: Long) {
withContext(coroutineContextProvider.io) {
doSomething(hereId)
}
}
But now, I got the error: "Suspension functions can be called only within coroutine body. Any suggestions?
Simple solution would be to mark both the block and handleIt function as suspending:
suspend fun handleIt(here: Long, call: suspend (hereId: Long) -> Unit) {
call(here)
}
Related
if i use :
suspend fun <T> A(block: suspend () -> T){
///std.
}
all things go right but :
suspend fun <T> (suspend () -> T).A(){
///std.
}.
no compilation errors , but i cant use it with suspend functions.
for example that we have this fun (do work is a suspend function):
accountManager.doWork(password)
in case #1 its works fine:
A {
accountManager.doWork(password)
}
in case #2 it does not work as expected(compilation error):
accountManager.doWork(password).A()
The receiver of
accountManager.doWork(password).A()
is whatever doWork(password) returns, not the doWork function itself. Let's suppose that doWork returns String, then the above would have worked if A were an extension function on String:
// Depending on what you do in its implementation,
// A does not need to be a suspending function
fun String.A() {
}
If you want A's receiver to be the function doWork instead, the syntax is:
(accountManager::doWork).A()
Then the above will compile if A is declared like this:
suspend fun <T, R> (suspend (T) -> R).A() {
}
Notice that the receiver type is a function that takes one parameter, since doWork takes one parameter.
If what you actually want to do is to use the entire suspending lambda you passed to A as a parameter here...
A {
accountManager.doWork(password)
}
...as the receiver of the new extension function A that you are declaring, then your attempt is correct:
suspend fun <T> (suspend () -> T).A(){
}
You should call it on a suspending lambda:
suspend { accountManager.doWork(password) }.A()
Though I'm not sure why you would prefer this to the way more readable A { ... } syntax.
I have a suspendable (updateData) function which takes another suspend function as an argument (transform). In my updateData function I'm making a call to an asynchronous API and I need to pass the result to the transform suspend function.
My current problem is that calling the transform function shows the message "suspension functions can be called only within coroutine context".
Here's what the code looks like:
override suspend fun updateData(transform: suspend (prefs: Preferences) -> Preferences): Preferences {
return suspendCancellableCoroutine { continuation ->
realtimeDatabase.runTransaction(object : Transaction.Handler {
override fun doTransaction(currentData: MutableData): Transaction.Result {
val prefs: Preferences = currentData.toPreferences()
// I need to call the transform() function here
// transform(prefs)
// This call shows the error "suspension functions can be called only within coroutine context"
return Transaction.success(currentData)
}
override fun onComplete(
error: DatabaseError?,
committed: Boolean,
currentData: DataSnapshot?
) {
if (error != null) {
continuation.resumeWithException(error)
} else {
continuation.resume(currentData.toPreferences())
}
}
})
}
}
I found this similar question, but it doesn't really solve my problem because I can't call the transform function outside of doTransaction (I need currentData).
Also, I can't make transform a normal "non-suspend" function because I'm overriding that function from another class.
My question is: How can I apply the transform suspend function to currentData?
I don't know exactly what your API is here, but maybe you can break this function up to do your transformation after the suspendCoroutine block so its being called inside the coroutine instead of in the API callback.
override suspend fun updateData(transform: suspend (prefs: Preferences) -> Preferences): Preferences {
val retrievedPrefs = suspendCancellableCoroutine { continuation ->
realtimeDatabase.runTransaction(object : Transaction.Handler {
override fun doTransaction(currentData: MutableData): Transaction.Result {
return Transaction.success(currentData)
}
override fun onComplete(
error: DatabaseError?,
committed: Boolean,
currentData: DataSnapshot?
) {
if (error != null) {
continuation.resumeWithException(error)
} else {
continuation.resume(currentData.toPreferences())
}
}
})
}
return transform(retrievedPrefs)
}
Why Kotlin can't reference to suspend function as the parameter of let/also and other functions?
class X
fun a(x: X) {}
suspend fun b(x: X) {}
X().let(::a)
X().let(::b) // Error: Type mismatch
You can only call suspend functions from a coroutine or another suspend function.
And let does not take a suspend function as a parameter.
public inline fun <T, R> T.let(block: (T) -> R): R
So as with any other type, the function declaration has to match. Passing a suspend function to another function that does not accept a suspend function will not work.
It would work when you have a function like:
This is just an example, no real usecase for a suspend function for printing a log!
suspend inline fun log(block: suspend () -> String) {
val message: String = block() // we assume block takes some time to be computed
return println(message) // once its available, we print it
}
You can use the log function like:
suspend fun complexError(): String {
// takes time to compute...
return "message"
}
// usage
suspend fun errorHandling() {
log(::complexError) // pass a reference to complexError()
// or
log() { complexError() }
}
With all the well-known single-function listeners we can use a simpler lambda notation
view.setOnClickListener { do() }
instead of the original, longer Java way of
view.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
do()
}
})
But what exactly makes this work? I tried to do the same with my own listener:
private var listener: OnCopyPasteClickListener? = null
interface OnCopyPasteClickListener {
fun onPasteClick(text: String)
}
fun setOnCopyPasteClickListener(onCopyPasteClickListener: OnCopyPasteClickListener) {
listener = onCopyPasteClickListener
}
and while the long approach works just fine:
copypaste.setOnCopyPasteClickListener(object : CopyPasteMenu.OnCopyPasteClickListener {
override fun onPasteClick(text: String) {
do(text)
}
})
I can't make it accept the short one:
copypaste.setOnCopyPasteClickListener {
do(it)
}
The IDE gives a type mismatch error.
Actually, if you have only one function to be invoked, I recommend you use Kotlin Callback.
typealias OnDoWorkListener = ((String) -> Unit)
class Work {
var doWork: OnDoWorkListener? = null
fun doSomething() {
doWork?.invoke("Message Here")
}
}
And in your function, you just set the callback to it
fun main() {
val work = Work()
work.doWork = {
Log.d("WORK", "This gets called from the `work` object. Message: $it")
}
work.doSomething();
}
We can also use function to set the listener as well.
class Work {
var doWork: OnDoWorkListener? = null
fun doSomething() {
doWork?.invoke("Message Here")
}
fun setOnWorkListener(listener: OnDoWorkListener) {
doWork = listener
}
}
fun main() {
val work = Work()
work.setOnWorkListener {
Log.d("WORK", "This gets called from the `work` object. Message: $it")
}
work.doSomething()
}
Higher order functions make this work:
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.
From the same page:
Passing a lambda to the last parameter
In Kotlin, there is a convention that if the last parameter of a
function accepts a function, a lambda expression that is passed as the
corresponding argument can be placed outside the parentheses:
val product = items.fold(1) { acc, e -> acc * e }
If the lambda is the only argument to that call, the parentheses can
be omitted entirely:
run { println("...") }
Knowing this, a possible update on your class would look like:
class CopyPaste {
private var listener: (String) -> Unit = {}
fun setOnCopyPasteClickListener(onCopyPasteClickListener: (String) -> Unit) {
listener = onCopyPasteClickListener
}
fun doCopyPaste(value: String) {
listener.invoke(value)
}
}
fun main() {
val copyPaste = CopyPaste()
copyPaste.setOnCopyPasteClickListener { println(it) }
copyPaste.doCopyPaste("ClipboardContent!")
}
The class CopyPaste stores the listener, which is a function that takes a String parameter and does not return anything. Its function setOnCopyPasteClickListener accepts a function with the same signature as the listener property and at the end doCopyPaste accepts a String parameter and passes it to the stored function.
Actually, just after I posted, I searched for more thoughts and found this thread: https://youtrack.jetbrains.com/issue/KT-7770 This is indeed a debated limitation as it currently only applies to Java, not Kotlin itself. There is also a suggestion there that gives almost the required simplicity:
interface OnCopyPasteClickListener {
fun onPasteClick(text: String)
companion object {
inline operator fun invoke(crossinline op: (text: String) -> Unit) =
object : OnCopyPasteClickListener {
override fun onPasteClick(text: String) = op(text)
}
}
}
and then, thanks to this overloaded operator, it can be called as:
copypaste.setOnCopyPasteClickListener(CopyPasteMenu.OnCopyPasteClickListener { text ->
do(text)
})
But as the suggested answers offer a more idiomatic solution, I'll accept one of those, I only wanted to include this approach here for reference.
I can define an inline function in Kotlin with:
inline fun func(a: () -> Unit, b: () -> Unit){
a()
b()
}
But how do I call this function?
For a normal inline function with only one parameter, I would use:
func {
doSomething()
}
Is there a similar syntax for functions with more than one inline parameter?
Only the last function parameter is passed outside the call operator.
For example:
fun foo(a: () -> Unit, b: () -> Unit)
foo({ println("hello") }) {
println("world")
}
The other function parameters are just passed in the normal argument list inside the parenthesis, the last can optionally be moved outside of those parenthesis as is done with most calls.
There are several ways to achieve this.
Probably the nicest is to use a bound function reference for the parameters before the last one:
fun foo(){
func(::foo){foo()}
//this also works:
func(::foo, ::foo)
//or place the other call within parentheses in a lambda. (you can only put the last lambda behind the method call.
func( { foo() } ){foo()}
}
inline fun func(a: () -> Unit, b: () -> Unit){
a()
b()
}
If you want to call an objects method just place that objects name in front of the ::
class Bar {
val baz = Baz()
fun foo() {
func(baz::func2, ::foo)
}
fun func(a: () -> Unit, b: () -> Unit) {
a()
b()
}
}
class Baz{
fun func2(){}
}