Как вернуть значение ответа из сопрограммы - PullRequest
0 голосов
/ 09 января 2020

Я недавно работал с Kotlin, и застрял с этой одной проблемой. Я пытаюсь вернуть значение с плавающей точкой, получить onResponse функции вызова сопрограммы API. Я пытаюсь создать класс, который обрабатывает вызов 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 равно нулю и не получит значение, полученное на Response. Только после того, как я попытаюсь вызвать эту функцию снова, значение будет обновлено. Я не уверен, что я делаю это правильно, и я пытался искать решения, но это не соответствует моей текущей ситуации. Наверное, мои навыки поиска очень плохи.

Вывод


TEST
value B: null

Как мне дождаться, пока сопрограмма завершится до sh, прежде чем вернуть значение cardBalance?

Ответы [ 3 ]

1 голос
/ 10 января 2020

Правильный способ вернуть одно значение из сопрограммы - использовать await().

Теперь, поскольку вы используете сопрограмму для переноса некоторого API обратного вызова, это не сработает так хорошо. Поэтому я бы предложил go примерно так:

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 thread только при необходимости, используя withContext(Dispatchers.Main). В противном случае вы просто запускаете IO в главном потоке, сопрограмме или нет.

Во-вторых, использование GlobalScope - плохая практика, и вам следует избегать ее любой ценой. Вместо этого я создаю пользовательскую область видимости, которую вы можете .cancel() для предотвращения утечки сопрограммы.

В-третьих, наиболее правильным способом было бы вернуть Deferred<Float>, а не Float, поскольку await() блокирует. Но я оставил это для простоты.

1 голос
/ 05 мая 2020

Чтобы решить мою маленькую проблему, я использовал обратный вызов для передачи данных ответа. Я обнаружил, что этот метод работает отлично и легче понять для моего уровня понимания. Этот скелет метода также можно использовать повторно для любого вызова службы 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)
                }
            }
        })
    }

    ....

}

Это несколько простых изменений, которые я сделал для этой конкретной проблемы в моем первом посте. Этот подход может быть не таким хорошим или достаточно плавным, как я все еще учусь. Надеюсь, это поможет некоторым из вас, ребята. ура

0 голосов
/ 09 января 2020

Сделать 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)
...