首页 > 解决方案 > Kotlin 使用协程处理改造请求

问题描述

我正在制作一个 Android 应用程序,我正在尝试登录。我提出了一个基本的改装请求,它可以工作,但我想用一个通用类处理来自服务器的响应,以向用户显示错误(例如电子邮件/密码错误)。我遵循本教程https://blog.mindorks.com/using-retrofit-with-kotlin-coroutines-in-android但在这里他在 viewModel 中提出请求并访问存储在 mainActivity 中 Resource 中的数据(视图班级)。我想访问 viewModel 中的数据以在共享首选项中保存一些信息(查看第一个代码块中的注释),但我不知道该怎么做。有人可以解释一下如何更改代码以从 ViewModel 访问 Resource 中的数据吗?这是我的视图模型:

class LoginViewModel(private val loginRepo: LoginRepository) : ViewModel() {
private fun makeLogin(email: String, password: String) {
        viewModelScope.launch {
            Resource.loading(data = null)
            try {

                val usr = User(email, password)
                Resource.success(data = loginRepo.makeLogin(usr))
                // HERE I WANT TO ACCESS TO DATA TO STORE SOME INFORMATION IN SHARED PREFERENCES
            } catch (ex: Exception) {
                Resource.error(data = null, message = ex.message ?: "Error occured!")
            }
}

这是资源类:

data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
    companion object {
        fun <T> success(data: T): Resource<T> = Resource(status = Status.SUCCESS, data = data, message = null)

        fun <T> error(data: T?, message: String): Resource<T> =
            Resource(status = Status.ERROR, data = data, message = message)

        fun <T> loading(data: T?): Resource<T> = Resource(status = Status.LOADING, data = data, message = null)
    }
}

这是存储库:

class LoginRepository(private val apiHelper: ApiHelper) {
    suspend fun makeLogin(usr: User) = apiHelper.makeLogin(usr)
}

apiHelper.makeLogin(usr) 的返回类型是:


@JsonClass(generateAdapter = true)
data class LoginResponse(
    val token: String,
    val expiration: String,
    val id : Int,
    val role: Int)

本教程的视图模型

class MainViewModel(private val mainRepository: MainRepository) : ViewModel() {

    fun getUsers() = liveData(Dispatchers.IO) {
        emit(Resource.loading(data = null))
        try {
            emit(Resource.success(data = mainRepository.getUsers()))
        } catch (exception: Exception) {
            emit(Resource.error(data = null, message = exception.message ?: "Error Occurred!"))
        }
    }
}

在教程中,他访问存储在主要活动中的资源中的数据,如下所示:

viewModel.getUsers().observe(this, Observer {
            it?.let { resource ->
                when (resource.status) {
                    SUCCESS -> {
                        recyclerView.visibility = View.VISIBLE
                        progressBar.visibility = View.GONE
                        resource.data?.let { users -> retrieveList(users) }
                    }
                    ERROR -> {
                        recyclerView.visibility = View.VISIBLE
                        progressBar.visibility = View.GONE
                        Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
                    }
                    LOADING -> {
                        progressBar.visibility = View.VISIBLE
                        recyclerView.visibility = View.GONE
                    }
                }
            }
        })

标签: androidkotlinretrofit

解决方案


在我看来loading状态不是响应状态,是视图的状态,所以我更喜欢避免放一个无用Loading class的来跟踪调用的加载状态。如果你使用协程,我猜,你知道调用何时处于loading状态,因为你正在执行一个挂起函数。

所以,对于这个问题,我觉得有用的是sealed class为响应定义一个泛型,它可以是类型SuccessError

sealed class Result<out R> {

    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()

    override fun toString(): String {
        return when (this) {
            is Success<*> -> "Success[data=$data]"
            is Error -> "Error[exception=$exception]"
        }
    }
}

然后我在我的数据源中使用这个类,返回一个Result.Success(带有它的数据)或一个Result.Error(带有它的异常消息)

override suspend fun getCities(): Result<List<City>> = withContext(Dispatchers.IO) {
        try {
            val response = service.getCities()
            if (response.isSuccessful) {
                val result = Result.Success(response.body()!!.cities)
                return@withContext result
            } else {
                return@withContext Result.Error(Exception(Exceptions.SERVER_ERROR))
            }
        } catch (e: Exception) {
            return@withContext Result.Error(e)
        }
    }

ViewModel我只是有一个observable视图的“加载状态”,我在调用挂起函数之前和之后发布了那个 observable 的更新:

class ForecastsViewModel @ViewModelInject constructor(
    private val citiesRepository: CitiesRepository) : ViewModel() {
    
    private val _dataLoading = MutableLiveData(false)
    val dataLoading: LiveData<Boolean> = _dataLoading
    
    private val _error = MutableLiveData<String>()
    val error: LiveData<String> = _error

    private val _cities = MutableLiveData<List<City>>()

    val cities: LiveData<List<City>> = _cities
    
    // The view calls this method and observes dataLoading to change state
    fun loadCities() {
            viewModelScope.launch {
                _dataLoading.value = true
                when (val result = citiesRepository.getCities(true)) {
                    is Result.Success -> {
                        citiesDownloaded.postValue(true)
                    }
                    is Result.Error -> {
                        _error.postValue(result.exception.message)
                    }
                }
                _dataLoading.value = false
            }
    }

}

如果你想深入了解代码,请查看我的github repo 关于这个主题


推荐阅读