Какова лучшая практика для обработки ошибок от конечных точек API в архитектуре MVVM? - PullRequest
0 голосов
/ 04 декабря 2018

Моя цель (а также вопрос) состоит в том, чтобы, скажем, централизованно обрабатывать ошибки.В большинстве случаев ошибки для каждой конечной точки API будут обрабатываться одинаково, поэтому я не хочу иметь дубликаты или много операторов if else.

Архитектура моего приложения соответствует архитектуре, описанной в developer.android.com

enter image description here

Итак,означает, что я должен передать ошибки от repo через viewModel до UI layer (Activity/Fragment), чтобы сделать изменения пользовательского интерфейса с этого уровня.

Некоторые маленькие части из моего кода:

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);
                    }
                });

Допустим, нам нужно показывать Toast для каждого вызова метода onFailure(...) или когда errorBody не будет null в onResponse(...) метод для каждого вызова API.

Итак, какие будут предложения по "централизованной" обработке ошибок при сохранении архитектуры такой, какая она есть сейчас?

Ответы [ 2 ]

0 голосов
/ 10 декабря 2018

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 с локальным источником данных.

0 голосов
/ 04 декабря 2018

Я думаю, что лучшим решением является создание объекта liveata в модели представления для передачи ошибок.Чем вы можете наблюдать эти ошибки в любом месте.

...