Migrate callback code to suspended function
Asked Answered
A

1

6

I'm re-factoring my Android code from Java to Kotlin using coroutines but i did not find an easy way to rewrite callback-based code to suspended functions.

A basic example would be an alert popup that returns a result, in Javascript it would be something like this:

let value = prompt("please insert a value")
console.log("Value:"+value)

I would translate in Kotlin to something like:

class MainActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        //Standard activity initialization
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //Actual code...
        launch {
            val value = resolvable<String>(UI) { success, error ->
                //Build a simple popup prompt with AlertDialog
                val input = EditText(this@MainActivity)

                val builder = AlertDialog.Builder(this@MainActivity)
                        .setTitle("please insert a value")
                        .setView(input)
                        .setPositiveButton("Ok",{ dialog, id ->
                            success(input.text.toString())//This lambda returns the value
                        })
                val dialog = builder.create()
                dialog.show()
            }
            println("Value:"+ value)
        }
        //...
    }
}

The resolvable is a custom function i developed for this purpuse, here's the source code:

import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.cancelAndJoin
import kotlinx.coroutines.experimental.launch
import java.util.concurrent.Semaphore
import kotlin.coroutines.experimental.CoroutineContext

suspend fun <T> resolvable(
        context: CoroutineContext = DefaultDispatcher,
        block: suspend (success:(T?)->Unit,error:(Throwable)->Unit) -> Unit
):T?{
    var result:T? = null
    var exception:Throwable? = null
    val semaphore = Semaphore(0)


    val job = launch(context){
        block({r:T? -> {
            result=r
            semaphore.release()
        }},{e:Throwable -> {
            exception=e
            semaphore.release()
        }})
    }

    semaphore.acquire()
    job.cancelAndJoin()

    if(exception!=null)
        throw exception!!
    return result
}

I quickly developed the resolvable function (keep in mind it's a quick draft) using lambdas and semaphores but i don't know if there are any pre-existing function (i could not found any) or can be optimized or has any drawback/problems.

Thanks.

Alfonsoalfonzo answered 22/1, 2018 at 10:10 Comment(2)
#44821034Jerri
@Jerri , that's NOT what i meant! in the example the code is simply ported from Java to Kotlin; i want to avoid using callback pattern and use a suspended function instead.Alfonsoalfonzo
C
12

It looks that you are trying to reinvent suspendCoroutine function. I'd suggest to replace your resolvable function with invocation of suspendCoroutine to get the kind of functionality you are looking for:

    //Actual code...
    launch(UI) {
        val value = suspendCoroutine<String> { cont ->
            //Build a simple popup prompt with AlertDialog
            val input = EditText(this@MainActivity)

            val builder = AlertDialog.Builder(this@MainActivity)
                    .setTitle("please insert a value")
                    .setView(input)
                    .setPositiveButton("Ok",{ dialog, id ->
                        cont.resume(input.text.toString()) //!!! Resume here
                    })
            val dialog = builder.create()
            dialog.show()
        }
        println("Value:"+ value)
    }

If you perform "extract function" refactoring around suspendCoroutine block and name the resulting suspending function prompt, then you can write your code in a style that is very similar to JS.

You can also consider using suspendCancellebleCoroutine instead of a plain suspendCoroutine. This way you can support cancellation of the launched coroutine and install a handler to close your dialog when it is cancelled.

Commissariat answered 23/1, 2018 at 9:39 Comment(4)
Thanks! i knew it was present but i could not find it in any documentation! Is there any way to specify the UI thread like in my example or i should just do a launch(UI) { suspendCoroutin<String>...... } ?Alfonsoalfonzo
You should specify the thread in the coroutine you launch. I've corrected the code in my answer.Commissariat
Please @Roman could you also post a suspendCancellebleCoroutine example, in case of cancellation close the dialog or just add a println.Alfonsoalfonzo
Is there some official docs for this pattern? It looks interesting, but can't find some official documentation about this.Caerphilly

© 2022 - 2024 — McMap. All rights reserved.