如何 return 来自协程的响应值
How to return response value from coroutine
我最近在使用 Kotlin,并且真的被这个问题困住了。我正在尝试 return 浮点值接收协程 api 调用函数的 onResponse。我正在尝试创建一个 class 来处理 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
为 null 并且没有获得 onResponse 获得的值。只有在我再次尝试调用该 functionA 之后,该值才会更新。我不确定我是否做对了,我试图寻找解决方案,但它不适合我目前的情况。可能是我搜索技术太差了
输出
TEST
value B: null
我应该如何等待协程在 return cardBalance
值之前完成?
将 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)
return 来自协程的单个值的正确方法是使用 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
,并且仅在需要时使用 withContext(Dispatchers.Main)
切换到 Main
线程。否则,你只是 运行 你在主线程上的 IO,协程与否。
其次,使用 GlobalScope
是一种不好的做法,您应该不惜一切代价避免它。相反,我创建了一个自定义范围,您可以 .cancel()
防止协程泄漏。
第三,最正确的方法是 return Deferred<Float>
,而不是 Float
,因为 await()
是阻塞的。但为了简单起见,我保留了它。
为了解决我的小问题,我最终使用回调来传递响应数据。我发现这种方法非常有效,而且就我的理解水平而言更容易理解。此方法框架也可重复用于我将来要使用的任何 api 服务调用。
FunctionA.kt
class FunctionA(val context: Context?, val A: Float?, val B: String?){
private var cardApi: CardApi = ApiClient.createApi().create(CardApi::class.java)
private var card: Card? = null
interface CardBalanceCallback {
fun processFinish(output: Boolean, cardBalance: Float?)
}
fun getCardBalance(cardNo: String, callback: CardBalanceCallback) = runBlocking {
getBalance(cardNo, callback)
}
private fun getBalance(cardNo: String, callback: CardBalanceCallback) = CoroutineScope(Dispatchers.Main).launch {
try {
val response = cardApi.getCardBalance(cardNo).await()
if (response.isSuccessful) {
card = response.body()
callback.processFinish(true, card!!.cardAvailableBalance)
} else {
callback.processFinish(false, null)
val error: ApiError = ErrorUtils.parseError(response)
val message = when {
error.error.code.isNotEmpty() -> error.error.code + error.error.message
else -> error.code + error.message
}
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
promptErrorDialog(error)
}
} catch (e: HttpException) {
callback.processFinish(false, null)
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to e.message!!)
context!!.toast(e.message.toString())
} catch (e: Throwable) {
callback.processFinish(false, null)
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to e.message!!)
context!!.toast( e.message.toString())
}
}
....
....
}
FragmentClass.kt
class FragmentClass : BaseFragment(){
private var funcService = FunctionA(null, null ,null)
....
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity!!.application as App).component.inject(this)
val valA = 10.5f
val valB = "Test"
val cardNo = "4001526976443264"
val cardExpDate = "1119"
funcService = FunctionA(context!!, valA ,valB)
getCardBalanceApi(cardNo, cardExpDate)
}
....
private fun getCardBalanceApi(cardNo: String, cardExpDate: String?) {
showLoadingDialog()
funcService.getCardBalance(cardNo, object : SmartPayService.CardBalanceCallback {
override fun processFinish(output: Boolean, cardBalance: Float?) {
dismissLoadingDialog()
if (cardBalance != null) {
checkBalance(cardNo, cardBalance, cardExpDate)
}
}
})
}
....
}
这是我在第一个 post 中针对这个特定问题所做的一些简单更改。这种方法可能不够好或不够流畅,因为我还在学习。希望它能帮助你们中的一些人。干杯
我最近在使用 Kotlin,并且真的被这个问题困住了。我正在尝试 return 浮点值接收协程 api 调用函数的 onResponse。我正在尝试创建一个 class 来处理 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
为 null 并且没有获得 onResponse 获得的值。只有在我再次尝试调用该 functionA 之后,该值才会更新。我不确定我是否做对了,我试图寻找解决方案,但它不适合我目前的情况。可能是我搜索技术太差了
输出
TEST
value B: null
我应该如何等待协程在 return cardBalance
值之前完成?
将 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)
return 来自协程的单个值的正确方法是使用 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
,并且仅在需要时使用 withContext(Dispatchers.Main)
切换到 Main
线程。否则,你只是 运行 你在主线程上的 IO,协程与否。
其次,使用 GlobalScope
是一种不好的做法,您应该不惜一切代价避免它。相反,我创建了一个自定义范围,您可以 .cancel()
防止协程泄漏。
第三,最正确的方法是 return Deferred<Float>
,而不是 Float
,因为 await()
是阻塞的。但为了简单起见,我保留了它。
为了解决我的小问题,我最终使用回调来传递响应数据。我发现这种方法非常有效,而且就我的理解水平而言更容易理解。此方法框架也可重复用于我将来要使用的任何 api 服务调用。
FunctionA.kt
class FunctionA(val context: Context?, val A: Float?, val B: String?){
private var cardApi: CardApi = ApiClient.createApi().create(CardApi::class.java)
private var card: Card? = null
interface CardBalanceCallback {
fun processFinish(output: Boolean, cardBalance: Float?)
}
fun getCardBalance(cardNo: String, callback: CardBalanceCallback) = runBlocking {
getBalance(cardNo, callback)
}
private fun getBalance(cardNo: String, callback: CardBalanceCallback) = CoroutineScope(Dispatchers.Main).launch {
try {
val response = cardApi.getCardBalance(cardNo).await()
if (response.isSuccessful) {
card = response.body()
callback.processFinish(true, card!!.cardAvailableBalance)
} else {
callback.processFinish(false, null)
val error: ApiError = ErrorUtils.parseError(response)
val message = when {
error.error.code.isNotEmpty() -> error.error.code + error.error.message
else -> error.code + error.message
}
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
promptErrorDialog(error)
}
} catch (e: HttpException) {
callback.processFinish(false, null)
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to e.message!!)
context!!.toast(e.message.toString())
} catch (e: Throwable) {
callback.processFinish(false, null)
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to e.message!!)
context!!.toast( e.message.toString())
}
}
....
....
}
FragmentClass.kt
class FragmentClass : BaseFragment(){
private var funcService = FunctionA(null, null ,null)
....
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity!!.application as App).component.inject(this)
val valA = 10.5f
val valB = "Test"
val cardNo = "4001526976443264"
val cardExpDate = "1119"
funcService = FunctionA(context!!, valA ,valB)
getCardBalanceApi(cardNo, cardExpDate)
}
....
private fun getCardBalanceApi(cardNo: String, cardExpDate: String?) {
showLoadingDialog()
funcService.getCardBalance(cardNo, object : SmartPayService.CardBalanceCallback {
override fun processFinish(output: Boolean, cardBalance: Float?) {
dismissLoadingDialog()
if (cardBalance != null) {
checkBalance(cardNo, cardBalance, cardExpDate)
}
}
})
}
....
}
这是我在第一个 post 中针对这个特定问题所做的一些简单更改。这种方法可能不够好或不够流畅,因为我还在学习。希望它能帮助你们中的一些人。干杯