Kotlin 使用协程处理改造请求
Kotlin handle retrofit request with coroutines
我正在制作一个 Android 应用程序,我正在尝试登录。我提出了一个基本的改装请求并且它有效,但我想用通用 class 处理来自服务器的响应,以向用户显示错误(例如 email/password 错误)。
我遵循本教程 https://blog.mindorks.com/using-retrofit-with-kotlin-coroutines-in-android 但在这里他在 viewModel 中发出请求并访问存储在 mainActivity 中的 Resource 中的数据(视图 class)。我想访问 viewModel 中的数据以在共享首选项中保存一些信息(查看第一个代码块中的注释),但我不知道该怎么做。
有人可以向我解释如何更改代码以从 ViewModel 访问 Resource 中的 data 吗?
这是我的视图模型:
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!")
}
}
这是资源 class:
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)的return类型是:
@JsonClass(generateAdapter = true)
data class LoginResponse(
val token: String,
val expiration: String,
val id : Int,
val role: Int)
教程的viewModel:
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!"))
}
}
}
在教程中,他像这样访问主要 activity 中存储在 Resource 中的数据:
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
}
}
}
})
我想您可以直接将响应分配给变量并在将其传递给 Resource
时访问它
private fun makeLogin(email: String, password: String) {
viewModelScope.launch {
Resource.loading(data = null)
try {
val usr = User(email, password)
val loginResponse = loginRepo.makeLogin(usr)
Resource.success(data = loginResponse)
//you can access loginResponse to access data inside it
} catch (ex: Exception) {
Resource.error(data = null, message = ex.message ?: "Error occured!")
}
}
}
您可以通过以下方式简单地做到这一点:
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)
val response = loginRepo.makeLogin(usr)
Resource.success(data = response)
// HERE YOU HAVE ACCESS TO RESPONSE TO DO WHATEVER YOU WANT WITH IT
} catch (ex: Exception) {
Resource.error(data = null, message = ex.message ?: "Error occured!")
}
}
处理您对所有请求的对象类型的响应,然后将该对象分配给您的模型class,或者按照您想要的方式解析对象
在我看来 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]"
}
}
}
然后我在我的数据源中使用这个 class,返回 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
,并且我 post 在调用挂起函数之前和之后对该可观察对象进行了更新:
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 存储库
如果没有发生 http 错误,必须弄清楚如何在 ViewModel 中获取响应并将数据发送到 Activity。保持简单并保持一切不变,除了:
1> 用 retrofit2.Response<*>
将您预期的 data class
return 包装在您的服务 class and/or 存储库中。只需将星号替换为预期的 return.
2> 更改您的 ViewModel 代码:
class MainViewModel(private val mainRepository: MainRepository) : ViewModel() {
fun getUsers() = liveData(Dispatchers.IO) {
emit(Resource.loading(data = null))
try {
val res: Response<*> = mainRepository.getUsers() // * Add data class Output expected
res?.let {response: Response<*> -> // * Same
if (response.isSuccessful) {
// TODO: Add code to set sharedpreferences
emit(Resource.success(data = response)) // return response or response.body()
} else {
throw retrofit2.HttpException(response)
}
}
} catch (exception: Exception) {
emit(Resource.error(data = null, message = exception.message ?: "Error Occurred!"))
}
}
}
我正在制作一个 Android 应用程序,我正在尝试登录。我提出了一个基本的改装请求并且它有效,但我想用通用 class 处理来自服务器的响应,以向用户显示错误(例如 email/password 错误)。 我遵循本教程 https://blog.mindorks.com/using-retrofit-with-kotlin-coroutines-in-android 但在这里他在 viewModel 中发出请求并访问存储在 mainActivity 中的 Resource 中的数据(视图 class)。我想访问 viewModel 中的数据以在共享首选项中保存一些信息(查看第一个代码块中的注释),但我不知道该怎么做。 有人可以向我解释如何更改代码以从 ViewModel 访问 Resource 中的 data 吗? 这是我的视图模型:
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!")
}
}
这是资源 class:
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)的return类型是:
@JsonClass(generateAdapter = true)
data class LoginResponse(
val token: String,
val expiration: String,
val id : Int,
val role: Int)
教程的viewModel:
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!"))
}
}
}
在教程中,他像这样访问主要 activity 中存储在 Resource 中的数据:
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
}
}
}
})
我想您可以直接将响应分配给变量并在将其传递给 Resource
private fun makeLogin(email: String, password: String) {
viewModelScope.launch {
Resource.loading(data = null)
try {
val usr = User(email, password)
val loginResponse = loginRepo.makeLogin(usr)
Resource.success(data = loginResponse)
//you can access loginResponse to access data inside it
} catch (ex: Exception) {
Resource.error(data = null, message = ex.message ?: "Error occured!")
}
}
}
您可以通过以下方式简单地做到这一点:
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)
val response = loginRepo.makeLogin(usr)
Resource.success(data = response)
// HERE YOU HAVE ACCESS TO RESPONSE TO DO WHATEVER YOU WANT WITH IT
} catch (ex: Exception) {
Resource.error(data = null, message = ex.message ?: "Error occured!")
}
}
处理您对所有请求的对象类型的响应,然后将该对象分配给您的模型class,或者按照您想要的方式解析对象
在我看来 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]"
}
}
}
然后我在我的数据源中使用这个 class,返回 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
,并且我 post 在调用挂起函数之前和之后对该可观察对象进行了更新:
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 存储库
如果没有发生 http 错误,必须弄清楚如何在 ViewModel 中获取响应并将数据发送到 Activity。保持简单并保持一切不变,除了:
1> 用 retrofit2.Response<*>
将您预期的 data class
return 包装在您的服务 class and/or 存储库中。只需将星号替换为预期的 return.
2> 更改您的 ViewModel 代码:
class MainViewModel(private val mainRepository: MainRepository) : ViewModel() {
fun getUsers() = liveData(Dispatchers.IO) {
emit(Resource.loading(data = null))
try {
val res: Response<*> = mainRepository.getUsers() // * Add data class Output expected
res?.let {response: Response<*> -> // * Same
if (response.isSuccessful) {
// TODO: Add code to set sharedpreferences
emit(Resource.success(data = response)) // return response or response.body()
} else {
throw retrofit2.HttpException(response)
}
}
} catch (exception: Exception) {
emit(Resource.error(data = null, message = exception.message ?: "Error Occurred!"))
}
}
}