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.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
Plokko
  • 723
  • 2
  • 10
  • 23
  • https://stackoverflow.com/questions/44820534/how-to-write-following-code-in-kotlin-for-callback-implementation – Sach Jan 22 '18 at 11:29
  • @Sach , 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. – Plokko Jan 22 '18 at 11:38

1 Answers1

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.

Roman Elizarov
  • 27,053
  • 12
  • 64
  • 60
  • 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...... } ? – Plokko Jan 23 '18 at 09:44
  • 1
    You should specify the thread in the coroutine you launch. I've corrected the code in my answer. – Roman Elizarov Jan 23 '18 at 09:53
  • Please @Roman could you also post a suspendCancellebleCoroutine example, in case of cancellation close the dialog or just add a println. – Plokko Jan 23 '18 at 17:18
  • Is there some official docs for this pattern? It looks interesting, but can't find some official documentation about this. – kravemir Jul 24 '22 at 15:23