如何从协程返回响应值

时间:2020-01-09 16:42:27

标签: android class kotlin fragment coroutine

我最近与Kotlin一起工作,并真的陷入了这一问题。我试图返回协程api调用函数的float值接收onResponse。我正在尝试创建一个处理api调用的类,并将其用于片段。

FunctionA.kt


class FunctionA(val context: Context?, val A: Float?, val B: String?){

   private var cardApi: CardApi = ApiClient.createApi().create(CardApi::class.java)

   ....

   func getBalance(cardNo: String): Float?{
       val cardBalance: Float = null

       GlobalScope.launch(Dispatchers.Main) {
            val cardDetails = cardApi.getCardBalance(cardNo)
            cardDetails.enqueue(object : Callback<Card> {
                override fun onFailure(call: Call<Card>, t: Throwable) {
                    trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to t.message!!)
                }

                override fun onResponse(call: Call<Card>, response: Response<Card>) {
                    if (response.isSuccessful) {
                        val card = response.body()!!
                        cardBalance = card.cardAvailableBalance

                    } else {
                        val error: ApiError = ErrorUtils.parseError(response)
                        val message = error.code + error.message
                        trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
                        context!!.toast("Errror: " + message)

                        promptErrorDialog(error)
                    }
                }
            })
        }}

        return cardBalance
   }
   ....
   ....

}

FragmentClass.kt


class FragmentClass : BaseFragment(){

    val galA = 10.5f
    val galB = "Test"
    private var pass = FunctionA(context!!, valA ,valB)

    ....

    val point = "sasd12125"
    private fun gooToo(){
        val B = pass.getBalance(point)
        print("TEST")
        println("value B: " + B)
    }
    ....

}

现在发生了什么,因为协程会在后台花费一些时间,val B为空,并且没有获得onResponse上获得的值。只有在我尝试再次调用该functionA之后,该值才会更新。我不确定我是否做对了,我已经尝试寻找解决方案,但是它不适合我目前的情况。可能我的搜索技能太差了。

输出


TEST
value B: null

在返回cardBalance值之前,我应该如何等待协程完成?

2 个答案:

答案 0 :(得分:1)

从协程返回单个值的正确方法是使用await()

现在,由于您使用协程包装了一些回调API,因此效果不佳。因此,我建议您使用类似这样的内容:

val scope = CoroutineScope(Dispatchers.IO)

suspend fun getBalance(cardNo: String): Float{
    val res = CompletableDeferred<Float>()

    scope.launch {
        val cardDetails = cardApi.getCardBalance(cardNo)
        cardDetails.enqueue(object : Callback<Card> {
            override fun onFailure(call: Call<Card>, t: Throwable) {
                trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to t.message!!)
            }

            override fun onResponse(call: Call<Card>, response: Response<Card>) {
                if (response.isSuccessful) {
                    val card = response.body()!!
                    res.complete(card.cardAvailableBalance)

                } else {
                    val error: ApiError = ErrorUtils.parseError(response)
                    val message = error.code + error.message
                    trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
                    res.completeExceptionally(message)

                    withContext(Dispatchers.Main) {
                        promptErrorDialog(error)    
                    }
                }
            }
        })
    }

    return res.await()
}

需要考虑的几点。首先,我使用Dispatchers.IO而不是Dispatchers.Main,并且仅在需要时使用Main切换到withContext(Dispatchers.Main)线程。否则,您只是在主线程上运行IO,无论协程是否正常。

第二,使用GlobalScope是一个不好的做法,您应该不惜一切代价避免使用它。相反,我创建了一个自定义范围,您可以.cancel()来防止协程泄漏。

第三,最正确的方法是返回Deferred<Float>,而不是Float,因为await()被阻止了。但是为了简单起见,我将其保留。

答案 1 :(得分:0)

使getBalance()成为暂停函数,然后在片段中使用lifecycleScope进行调用

private fun gooToo(){
   lifecycleScope.launch {
       val B = pass.getBalance(point)
       print("TEST")
       println("value B: " + B)
   }
}

getBalance()函数签名将类似于

suspend fun getBalance(): Float = withContext(Dispatchers.IO)