android - 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
}
}
}
})
解决方案
在我看来loading
状态不是响应状态,是视图的状态,所以我更喜欢避免放一个无用Loading class
的来跟踪调用的加载状态。如果你使用协程,我猜,你知道调用何时处于loading
状态,因为你正在执行一个挂起函数。
所以,对于这个问题,我觉得有用的是sealed class
为响应定义一个泛型,它可以是类型Success
或Error
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 关于这个主题
推荐阅读
- android - 像素密度如何与屏幕尺寸对应?
- java - 如何将 math.random() 与输入字符一起使用?
- html - role="presentation" 或 aria-hidden="true" 用于装饰图像?
- android - React native Touch 事件正在通过绝对视图
- php - 使用 php mysqli SELECT 检索数据时删除单引号
- microsoft-graph-api - 有没有办法从 Graph 中检索 OneNote 页面 InkNode 内容?
- python - 如何在python中使用plotnine打印分位数
- c# - Mongo C#,查询所有文档,其中数组包含特定元素
- javascript - 将通过 Google 表单提交的日期单元格值拆分为 Google 表格
- asp.net - 正则表达式模式的动态分配