Generic Retrofit Callback
Чтобы передать ошибки уровня репо в пользовательский интерфейс, можно объединить класс модели вместе с ошибкой в общую комбинированную модель, например:
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;
}
}
Inотдельный вспомогательный класс, мы определяем универсальный обратный вызов 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
}
}
Этот универсальный обратный вызов Retrofit можно использовать во всех местах, где вы вызываете API в своем хранилище.слой.Например:
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;
}
}
Украшение Observer
Теперь у вас есть универсальный способ использования вашего Retrofit API, и у вас есть LiveData, который оборачивает модели и ошибки.Эти LiveData поступают на уровень пользовательского интерфейса из ViewModel.Теперь мы украшаем наблюдателя реальных данных общей обработкой ошибок.
Сначала мы определяем интерфейс ErrorView, который можно реализовать, однако вы хотите показать свои ошибки пользователю.
interface ErrorView {
void showError(String message);
}
Это может быть реализовано путем отображения сообщения Toast, но вы можете свободно реализовать ErrorView со своим фрагментом и делать все, что хотите с сообщением об ошибке в вашем фрагменте.Мы используем отдельный класс, чтобы один и тот же класс можно было использовать в каждом фрагменте (используя композицию вместо наследования в качестве лучшей практики).
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 См. this для более подробной реализации Ресурса, объединяющей API с локальным источником данных.