【问题标题】:Passing errors coming from the API call传递来自 API 调用的错误
【发布时间】:2021-08-26 13:16:31
【问题描述】:

我使用 2 个单独的 liveData 暴露来显示来自 API 的错误。我基本上是在检查API调用是否有异常,传递一个失败状态并观察到serverErrorLiveData

所以我有 serverErrorLiveData 表示错误,creditReportLiveData 表示没有错误的结果。

我认为我这样做的方式不对。您能否指导我从 API 调用中捕获错误的正确方法。此外,关于将数据从存储库传递到视图模型的任何问题/建议。

处理加载状态的正确方法是什么?

CreditScoreFragment

    private fun initViewModel() {
    viewModel.getCreditReportObserver().observe(viewLifecycleOwner, Observer<CreditReport> {
        showScoreUI(true)
        binding.score.text = it.creditReportInfo.score.toString()
        binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
        initDonutView(
            it.creditReportInfo.score.toFloat(),
            it.creditReportInfo.maxScoreValue.toFloat()
        )
    })
    viewModel.getServerErrorLiveDataObserver().observe(viewLifecycleOwner, Observer<Boolean> {
        if (it) {
            showScoreUI(false)
            showToastMessage()
        }
    })
    viewModel.getCreditReport()
}

MainActivityViewModel

    class MainActivityViewModel @Inject constructor(
    private val dataRepository: DataRepository
) : ViewModel() {
    var creditReportLiveData: MutableLiveData<CreditReport>
    var serverErrorLiveData: MutableLiveData<Boolean>

    init {
        creditReportLiveData = MutableLiveData()
        serverErrorLiveData = MutableLiveData()
    }

    fun getCreditReportObserver(): MutableLiveData<CreditReport> {
        return creditReportLiveData
    }

    fun getServerErrorLiveDataObserver(): MutableLiveData<Boolean> {
        return serverErrorLiveData
    }

    fun getCreditReport() {
        viewModelScope.launch(Dispatchers.IO) {
            val response = dataRepository.getCreditReport()

            when(response.status) {
                CreditReportResponse.Status.SUCCESS -> creditReportLiveData.postValue(response.creditReport)
                CreditReportResponse.Status.FAILURE -> serverErrorLiveData.postValue(true)
            }
        }
    }
}

数据存储库

class DataRepository @Inject constructor(
        private val apiServiceInterface: ApiServiceInterface
) {

    suspend fun getCreditReport(): CreditReportResponse {
        return try {
            val creditReport = apiServiceInterface.getDataFromApi()
            CreditReportResponse(creditReport, CreditReportResponse.Status.SUCCESS)
        } catch (e: Exception) {
            CreditReportResponse(null, CreditReportResponse.Status.FAILURE)
        }
    }
}

ApiService接口

interface ApiServiceInterface {
    @GET("endpoint.json")
    suspend fun getDataFromApi(): CreditReport
}

信用评分响应

data class CreditReportResponse constructor(val creditReport: CreditReport?, val status: Status) {
    enum class Status {
        SUCCESS, FAILURE
    }
}

【问题讨论】:

    标签: android kotlin mvvm


    【解决方案1】:

    拥有两个 LiveData 通道来判断成功和失败会增加复杂性并增加发生编码错误的机会。您应该有一个 LiveData 可以提供数据或错误,以便您知道它正在有序地出现并且您可以在一个地方观察它。然后,如果您添加重试策略,例如,您将不会冒险在输入有效值后以某种方式显示错误。Kotlin 可以使用密封类以类型安全的方式促进这一点。但是您已经在使用包装类来衡量成功和失败。我认为你可以去源头并简化它。你甚至可以只使用 Kotlin 自己的 Result 类。

    (旁注,您的 getCreditReportObserver()getServerErrorLiveDataObserver() 函数是完全多余的,因为它们只是返回与属性相同的东西。您不需要 Kotlin 中的 getter 函数,因为属性基本上是 getter 函数,除了暂停 getter 函数,因为 Kotlin 不支持 suspend 属性。)

    因此,要做到这一点,请消除您的 CreditReportResponse 类。将您的回购功能更改为:

    suspend fun getCreditReport(): Result<CreditReport> = runCatching {
        apiServiceInterface.getDataFromApi()
    }
    

    如果您必须使用 LiveData(我认为不使用单个检索值更简单,见下文),您的 ViewModel 可能如下所示:

    class MainActivityViewModel @Inject constructor(
        private val dataRepository: DataRepository
    ) : ViewModel() {
        val _creditReportLiveData = MutableLiveData<Result<CreditReport>>()
        val creditReportLiveData: LiveData<Result<CreditReport>> = _creditReportLiveData 
    
        fun fetchCreditReport() { // I changed the name because "get" implies a return value
        // but personally I would change this to an init block so it just starts automatically
        // without the Fragment having to manually call it.
            viewModelScope.launch { // no need to specify dispatcher to call suspend function
                _creditReportLiveData.value = dataRepository.getCreditReport()
            }
        }
    }
    

    然后在你的片段中:

    private fun initViewModel() {
        viewModel.creditReportLiveData.observe(viewLifecycleOwner) { result -> 
            result.onSuccess {
                showScoreUI(true)
                binding.score.text = it.creditReportInfo.score.toString()
                binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
                initDonutView(
                    it.creditReportInfo.score.toFloat(),
                    it.creditReportInfo.maxScoreValue.toFloat()
                )
            }.onFailure {
                showScoreUI(false)
                showToastMessage()
            }
        viewModel.fetchCreditReport()
    }
    

    编辑:下面将简化您当前的代码,但使您无法轻松添加失败时的重试策略。保留 LiveData 可能更有意义。

    由于您只检索单个值,因此公开挂起函数而不是 LiveData 会更简洁。您可以私下使用 Deferred,这样如果屏幕旋转就不必重复获取(结果仍会到达并缓存在 ViewModel 中)。所以我会这样做:

    class MainActivityViewModel @Inject constructor(
        private val dataRepository: DataRepository
    ) : ViewModel() {
        private creditReportDeferred = viewModelScope.async { dataRepository.getCreditReport() }
    
        suspend fun getCreditReport() = creditReportDeferred.await()
    }
    
    // In fragment:
    private fun initViewModel() = lifecycleScope.launch {
        viewModel.getCreditReport()
            .onSuccess {
                showScoreUI(true)
                binding.score.text = it.creditReportInfo.score.toString()
                binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
                initDonutView(
                    it.creditReportInfo.score.toFloat(),
                    it.creditReportInfo.maxScoreValue.toFloat()
                )
            }.onFailure {
                showScoreUI(false)
                showToastMessage()
            }
    }
    

    【讨论】:

    • 非常感谢您的详细解释和建议,这是非常有帮助的和非常好的更正。
    • )4,感谢您的回复,当我在存储库中尝试此操作时,我得到'kotlin.Result' cannot be used as a return type
    • 抱歉解决了我不得不在 Gradle 中添加 freeCompilerArgs = ["-Xallow-result-return-type"] `kotlinOptions { jvmTarget = '1.8' freeCompilerArgs = ["-Xallow-result-return-type"] }`
    • 确保更新到最新的 Kotlin 版本(当前为 1.5.2)。那么你就不必用 kotlinOptions 来愚弄了。这是他们在 1.5 之前的版本中存在的限制。
    猜你喜欢
    • 2023-03-06
    • 2021-06-28
    • 1970-01-01
    • 2023-02-02
    • 1970-01-01
    • 2020-07-09
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多