android - 在 MVVM 架构中处理来自 api 端点的错误的最佳实践是什么?
问题描述
我的目标(也是问题)是做集中式错误处理。在大多数情况下,每个 API 端点的错误都将以相同的方式处理,因此我不希望有重复或大量if else
语句。
我的应用程序架构对应于developer.android.com中描述的架构
因此,这意味着我应该将错误从repo
via传递viewModel
到UI layer (Activity/Fragment)
,以便从该层进行 UI 更改。
我的代码中的一些小部分:
myService.initiateLogin("Basic " + base64, authBody)
.enqueue(new Callback<UserTokenModel>() {
@Override
public void onResponse(Call<UserTokenModel> call, Response<UserTokenModel> response) {
userTokenModelMutableLiveData.setValue(response.body());
}
@Override
public void onFailure(Call<UserTokenModel> call, Throwable t) {
// TODO better error handling in feature ...
userTokenModelMutableLiveData.setValue(null);
}
});
假设我们需要为每个onFailure(...)
方法调用显示 Toast,或者每个 api 调用何时errorBody
不在方法null
中。onResponse(...)
那么,在保持架构不变的同时进行“集中式”错误处理的建议是什么?
解决方案
通用改造回调
要将 repo 层错误传递给 UI,您可以将模型类与错误一起包装到通用组合模型中,如下所示:
class Resource<T> {
@Nullable private final T data;
@Nullable private final Throwable error;
private Resource(@Nullable T data, @Nullable Throwable error) {
this.data = data;
this.error = error;
}
public static <T> Resource<T> success(@NonNull T data) {
return new Resource<>(data, null);
}
public static <T> Resource<T> error(@NonNull Throwable error) {
return new Resource<>(null, error);
}
@Nullable
public T getData() {
return data;
}
@Nullable
public Throwable getError() {
return error;
}
}
在一个单独的帮助器类中,我们定义了一个通用的 Retrofit 回调来处理错误,并将 API 结果转换为资源。
class ResourceCallback {
public static <T> Callback<T> forLiveData(MutableLiveData<Resource<T>> target) {
return new Callback<T>() {
@Override
public void onResponse(Call<T> call, Response<T> response) {
if (!response.isSuccessful() || response.body() == null) {
target.setValue(Resource.error(convertUnsuccessfulResponseToException(response)));
} else {
target.setValue(Resource.success(response.body()));
}
}
@Override
public void onFailure(Call<T> call, Throwable t) {
// You could examine 't' here, and wrap or convert it to your domain specific exception class.
target.setValue(Resource.error(t));
}
};
}
private static <T> Throwable convertUnsuccessfulResponseToException(Response<T> response) {
// You could examine the response here, and convert it to your domain specific exception class.
// You can use
response.errorBody();
response.code();
response.headers();
// etc...
return new LoginFailedForSpecificReasonException(); // This is an example for a failed login
}
}
您可以在存储库层中调用 API 的所有位置使用此通用 Retrofit 回调。例如:
class AuthenticationRepository {
// ...
LiveData<Resource<UserTokenModel>> login(String[] params) {
MutableLiveData<Resource<UserTokenModel>> result = new MutableLiveData<>();
myService.initiateLogin("Basic " + base64, authBody).enqueue(ResourceCallback.forLiveData(result));
return result;
}
}
装饰观察者
现在,您有了使用 Retrofit API 的通用方式,并且有了包装模型和错误的 LiveData。此 LiveData 从 ViewModel 到达 UI 层。现在我们用通用错误处理来装饰实时数据的观察者。
首先,我们定义一个可以实现的 ErrorView 接口,但是您想向用户显示错误。
interface ErrorView {
void showError(String message);
}
这可以通过显示 Toast 消息来实现,但是您可以使用 Fragment 自由地实现 ErrorView 并使用 Fragment 上的错误消息做任何您想做的事情。我们使用一个单独的类,以便可以在每个 Fragment 中使用相同的类(使用组合而不是继承作为最佳实践)。
class ToastMessageErrorView implements ErrorView {
private Context context;
public ToastMessageErrorView(Context context) {
this.context = context;
}
@Override
public void showError(String message) {
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
}
现在我们实现了观察者装饰器,它包装了一个装饰过的观察者并用错误处理来装饰它,在发生错误时调用 ErrorView。
class ResourceObserver {
public static <T> Observer<Resource<T>> decorateWithErrorHandling(Observer<T> decorated, ErrorView errorView) {
return resource -> {
Throwable t = resource.getError();
if (t != null) {
// Here you should examine 't' and create a specific error message. For simplicity we use getMessage().
String message = t.getMessage();
errorView.showError(message);
} else {
decorated.onChanged(resource.getData());
}
};
}
}
在您的片段中,您可以像这样使用观察者装饰器:
class MyFragment extends Fragment {
private MyViewModel viewModel;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
viewModel.getUserToken().observe(this, ResourceObserver.decorateWithErrorHandling(
userTokenModel -> {
// Process the model
},
new ToastMessageErrorView(getActivity())));
}
}
PS 有关将 API 与本地数据源相结合的更详细的资源实现,请参阅此内容。
推荐阅读
- eclipse - 在 Eclipse 中自动运行启动文件
- java - 在从 VCS 签出后应用 API 更改作为重构(最好在 IntelliJ 平台内)
- vue.js - 如何删除 *.vue 文件中的所有注释和/或空格和/或空行
- r - 从数据框中的特定列中选择奇数行
- javascript - 将回调的结果按顺序沿链向下传递到外部回调
- sql - 为什么ROWNUM <=,左外连接和子查询的这种组合有时会给出null
- c# - 如何在 C# 中的多个 XML 文件中搜索 XElement 属性值?
- php - 使用 foreach 循环添加 DateTime 间隔
- ios - SwiftUI 中的 CALayer 等效项
- asterisk - “请选择 PBX 的默认区域设置”页面在 FreePBX 中经常出现