使用 Retrofit + Kotlin Flow 处理错误的优雅方式
Elegant way of handling error using Retrofit + Kotlin Flow
我最喜欢在 Android 上进行网络请求(使用 Retrofit)。看起来像这样:
// NetworkApi.kt
interface NetworkApi {
@GET("users")
suspend fun getUsers(): List<User>
}
在我的 ViewModel 中:
// MyViewModel.kt
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
val usersLiveData = flow {
emit(networkApi.getUsers())
}.asLiveData()
}
最后,在我的 Activity/Fragment:
//MyActivity.kt
class MyActivity: AppCompatActivity() {
private viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.usersLiveData.observe(this) {
// Update the UI here
}
}
}
我喜欢这种方式的原因是因为它原生使用 Kotlin 流程,非常易于使用,并且有很多有用的操作(flatMap 等)。
但是,我不确定如何使用这种方法优雅地处理网络错误。我能想到的一种方法是使用 Response<T>
作为网络 API 的 return 类型,像这样:
// NetworkApi.kt
interface NetworkApi {
@GET("users")
suspend fun getUsers(): Response<List<User>>
}
然后在我的视图模型中,我可以有一个 if-else 来检查响应的 isSuccessful
,如果是,则使用 .body()
API 获得真实结果成功的。但是当我在我的视图模型中进行一些转换时,它会出现问题。例如
// MyViewModel.kt
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
val usersLiveData = flow {
val response = networkApi.getUsers()
if (response.isSuccessful) {
emit(response.body()) // response.body() will be List<User>
} else {
// What should I do here?
}
}.map { // it: List<User>
// transform Users to some other class
it?.map { oneUser -> OtherClass(oneUser.userName) }
}.asLiveData()
注意评论“我应该在这里做什么?”。我不知道在那种情况下该怎么办。我可以用一些“状态”(或者只是传递响应本身)来包装 responseBody(在这种情况下,用户列表)。但这意味着我几乎必须使用 if-else 来检查流转换链中每一步的状态,一直到 UI。如果链条真的很长(比如我链上有10个map
或者flatMapConcat
),每一步都要做真的很烦
请问在这种情况下处理网络错误的最佳方法是什么?
您应该有一个 sealed class
来处理不同类型的事件。例如,Success
、Error
或 Loading
。这是一些适合您的用例的示例。
enum class ApiStatus{
SUCCESS,
ERROR,
LOADING
} // for your case might be simplify to use only sealed class
sealed class ApiResult <out T> (val status: ApiStatus, val data: T?, val message:String?) {
data class Success<out R>(val _data: R?): ApiResult<R>(
status = ApiStatus.SUCCESS,
data = _data,
message = null
)
data class Error(val exception: String): ApiResult<Nothing>(
status = ApiStatus.ERROR,
data = null,
message = exception
)
data class Loading<out R>(val _data: R?, val isLoading: Boolean): ApiResult<R>(
status = ApiStatus.LOADING,
data = _data,
message = null
)
}
然后,在您的 ViewModel 中,
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
// this should be returned as a function, not a variable
val usersLiveData = flow {
emit(ApiResult.Loading(true)) // 1. Loading State
val response = networkApi.getUsers()
if (response.isSuccessful) {
emit(ApiResult.Success(response.body())) // 2. Success State
} else {
val errorMsg = response.errorBody()?.string()
response.errorBody()?.close() // remember to close it after getting the stream of error body
emit(ApiResult.Error(errorMsg)) // 3. Error State
}
}.map { // it: List<User>
// transform Users to some other class
it?.map { oneUser -> OtherClass(oneUser.userName) }
}.asLiveData()
在你看来(Activity/Fragment),观察这些状态。
viewModel.usersLiveData.observe(this) { result ->
// Update the UI here
when(result.status) {
ApiResult.Success -> {
val data = result.data <-- return List<User>
}
ApiResult.Error -> {
val errorMsg = result.message <-- return errorBody().string()
}
ApiResult.Loading -> {
// here will actually set the state as Loading
// you may put your loading indicator here.
}
}
}
//这个class表示加载语句管理操作
/*
- 什么是密封class
- 密封的 class 是一个抽象 class,具有受限的 class 层次结构。
从它继承的 - 类 必须与密封的 class.
在同一个文件中
- 这提供了对继承的更多控制。它们受到限制,但也允许国家代表自由。
- Sealed classes可以嵌套数据classes,classes,对象,以及其他sealed classes.
- 自动完成功能在处理其他密封的 classes 时大放异彩。
- 这是因为 IDE 可以检测到这些 class 中的分支。
- */
ٍٍٍٍٍ
sealed class APIResponse<out T>{
class Success<T>(response: Response<T>): APIResponse<T>() {
val data = response.body()
}
class Failure<T>(response: Response<T>): APIResponse<T>() {
val message:String = response.errorBody().toString()
}
class Exception<T>(throwable: Throwable): APIResponse<T>() {
val message:String? = throwable.localizedMessage
}
}
创建名为 APIResponsrEX.kt 的扩展文件
并创建扩展方法
fun <T> APIResponse<T>.onSuccess(onResult :APIResponse.Success<T>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Success) onResult(this)
return this
}
fun <T> APIResponse<T>.onFailure(onResult: APIResponse.Failure<*>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Failure<*>)
onResult(this)
return this
}
fun <T> APIResponse<T>.onException(onResult: APIResponse.Exception<*>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Exception<*>) onResult(this)
return this
}
将其与 Retrofit 合并
inline fun <T> Call<T>.request(crossinline onResult: (response: APIResponse<T>) -> Unit) {
enqueue(object : retrofit2.Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
// success
onResult(APIResponse.Success(response))
} else {
//failure
onResult(APIResponse.Failure(response))
}
}
override fun onFailure(call: Call<T>, throwable: Throwable) {
onResult(APIResponse.Exception(throwable))
}
})
}
我最喜欢在 Android 上进行网络请求(使用 Retrofit)。看起来像这样:
// NetworkApi.kt
interface NetworkApi {
@GET("users")
suspend fun getUsers(): List<User>
}
在我的 ViewModel 中:
// MyViewModel.kt
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
val usersLiveData = flow {
emit(networkApi.getUsers())
}.asLiveData()
}
最后,在我的 Activity/Fragment:
//MyActivity.kt
class MyActivity: AppCompatActivity() {
private viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.usersLiveData.observe(this) {
// Update the UI here
}
}
}
我喜欢这种方式的原因是因为它原生使用 Kotlin 流程,非常易于使用,并且有很多有用的操作(flatMap 等)。
但是,我不确定如何使用这种方法优雅地处理网络错误。我能想到的一种方法是使用 Response<T>
作为网络 API 的 return 类型,像这样:
// NetworkApi.kt
interface NetworkApi {
@GET("users")
suspend fun getUsers(): Response<List<User>>
}
然后在我的视图模型中,我可以有一个 if-else 来检查响应的 isSuccessful
,如果是,则使用 .body()
API 获得真实结果成功的。但是当我在我的视图模型中进行一些转换时,它会出现问题。例如
// MyViewModel.kt
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
val usersLiveData = flow {
val response = networkApi.getUsers()
if (response.isSuccessful) {
emit(response.body()) // response.body() will be List<User>
} else {
// What should I do here?
}
}.map { // it: List<User>
// transform Users to some other class
it?.map { oneUser -> OtherClass(oneUser.userName) }
}.asLiveData()
注意评论“我应该在这里做什么?”。我不知道在那种情况下该怎么办。我可以用一些“状态”(或者只是传递响应本身)来包装 responseBody(在这种情况下,用户列表)。但这意味着我几乎必须使用 if-else 来检查流转换链中每一步的状态,一直到 UI。如果链条真的很长(比如我链上有10个map
或者flatMapConcat
),每一步都要做真的很烦
请问在这种情况下处理网络错误的最佳方法是什么?
您应该有一个 sealed class
来处理不同类型的事件。例如,Success
、Error
或 Loading
。这是一些适合您的用例的示例。
enum class ApiStatus{
SUCCESS,
ERROR,
LOADING
} // for your case might be simplify to use only sealed class
sealed class ApiResult <out T> (val status: ApiStatus, val data: T?, val message:String?) {
data class Success<out R>(val _data: R?): ApiResult<R>(
status = ApiStatus.SUCCESS,
data = _data,
message = null
)
data class Error(val exception: String): ApiResult<Nothing>(
status = ApiStatus.ERROR,
data = null,
message = exception
)
data class Loading<out R>(val _data: R?, val isLoading: Boolean): ApiResult<R>(
status = ApiStatus.LOADING,
data = _data,
message = null
)
}
然后,在您的 ViewModel 中,
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
// this should be returned as a function, not a variable
val usersLiveData = flow {
emit(ApiResult.Loading(true)) // 1. Loading State
val response = networkApi.getUsers()
if (response.isSuccessful) {
emit(ApiResult.Success(response.body())) // 2. Success State
} else {
val errorMsg = response.errorBody()?.string()
response.errorBody()?.close() // remember to close it after getting the stream of error body
emit(ApiResult.Error(errorMsg)) // 3. Error State
}
}.map { // it: List<User>
// transform Users to some other class
it?.map { oneUser -> OtherClass(oneUser.userName) }
}.asLiveData()
在你看来(Activity/Fragment),观察这些状态。
viewModel.usersLiveData.observe(this) { result ->
// Update the UI here
when(result.status) {
ApiResult.Success -> {
val data = result.data <-- return List<User>
}
ApiResult.Error -> {
val errorMsg = result.message <-- return errorBody().string()
}
ApiResult.Loading -> {
// here will actually set the state as Loading
// you may put your loading indicator here.
}
}
}
//这个class表示加载语句管理操作 /*
- 什么是密封class
- 密封的 class 是一个抽象 class,具有受限的 class 层次结构。 从它继承的
- 类 必须与密封的 class. 在同一个文件中
- 这提供了对继承的更多控制。它们受到限制,但也允许国家代表自由。
- Sealed classes可以嵌套数据classes,classes,对象,以及其他sealed classes.
- 自动完成功能在处理其他密封的 classes 时大放异彩。
- 这是因为 IDE 可以检测到这些 class 中的分支。
- */
ٍٍٍٍٍ
sealed class APIResponse<out T>{
class Success<T>(response: Response<T>): APIResponse<T>() {
val data = response.body()
}
class Failure<T>(response: Response<T>): APIResponse<T>() {
val message:String = response.errorBody().toString()
}
class Exception<T>(throwable: Throwable): APIResponse<T>() {
val message:String? = throwable.localizedMessage
}
}
创建名为 APIResponsrEX.kt 的扩展文件 并创建扩展方法
fun <T> APIResponse<T>.onSuccess(onResult :APIResponse.Success<T>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Success) onResult(this)
return this
}
fun <T> APIResponse<T>.onFailure(onResult: APIResponse.Failure<*>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Failure<*>)
onResult(this)
return this
}
fun <T> APIResponse<T>.onException(onResult: APIResponse.Exception<*>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Exception<*>) onResult(this)
return this
}
将其与 Retrofit 合并
inline fun <T> Call<T>.request(crossinline onResult: (response: APIResponse<T>) -> Unit) {
enqueue(object : retrofit2.Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
// success
onResult(APIResponse.Success(response))
} else {
//failure
onResult(APIResponse.Failure(response))
}
}
override fun onFailure(call: Call<T>, throwable: Throwable) {
onResult(APIResponse.Exception(throwable))
}
})
}