Подтвердить что ты не робот

Как обрабатывать состояния ошибок с помощью LiveData?

Новый LiveData можно использовать в качестве замены для наблюдаемых RxJava в некоторых сценариях. Однако, в отличие от Observable, LiveData не имеет обратного вызова для ошибок.

Мой вопрос: как я должен обрабатывать ошибки в LiveData, например. когда он поддерживается каким-то сетевым ресурсом, который может не получить из-за IOException?

4b9b3361

Ответ 1

В одном из примеров приложений Google для компонентов архитектуры Android они обертывают отправленный объект LiveData в класс, который может содержать состояние, данные и сообщение для испускаемого объекта.

https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.kt

При таком подходе вы можете использовать статус, чтобы определить, произошла ли ошибка.

Ответ 2

Оберните данные, которые вы возвращаете из LiveData с некоторой ошибкой. Сообщения

public class DataWrapper<T>T{
    private T data;
    private ErrorObject error; //or A message String, Or whatever
}

//Теперь в вашем LifecycleRegistryOwner классе

LiveData<DataWrapper<SomeObjectClass>> result = modelView.getResult();

result.observe(this, newData ->{
    if(newData.error != null){ //Can also have a Status Enum
        //Handle Error
    }
    else{
       //Handle data
    }

});

Просто возьмите Exception или выбросьте его. используйте объект Error для передачи этих данных в пользовательский интерфейс.

MutableLiveData<DataWrapper<SomObject>> liveData = new...;

//On Exception catching:
liveData.set(new DataWrapper(null, new ErrorObject(e));

Ответ 3

Вы можете выйти из MutableLiveData и создать модель держателя для переноса ваших данных.

Это твоя модель обертки

public class StateData<T> {

    @NonNull
    private DataStatus status;

    @Nullable
    private T data;

    @Nullable
    private Throwable error;

    public StateData() {
        this.status = DataStatus.CREATED;
        this.data = null;
        this.error = null;
    }

    public StateData<T> loading() {
        this.status = DataStatus.LOADING;
        this.data = null;
        this.error = null;
        return this;
    }

    public StateData<T> success(@NonNull T data) {
        this.status = DataStatus.SUCCESS;
        this.data = data;
        this.error = null;
        return this;
    }

    public StateData<T> error(@NonNull Throwable error) {
        this.status = DataStatus.ERROR;
        this.data = null;
        this.error = error;
        return this;
    }

    public StateData<T> complete() {
        this.status = DataStatus.COMPLETE;
        return this;
    }

    @NonNull
    public DataStatus getStatus() {
        return status;
    }

    @Nullable
    public T getData() {
        return data;
    }

    @Nullable
    public Throwable getError() {
        return error;
    }

    public enum DataStatus {
        CREATED,
        SUCCESS,
        ERROR,
        LOADING,
        COMPLETE
    }
}

Это ваш расширенный объект LiveData

public class StateLiveData<T> extends MutableLiveData<StateData<T>> {

    /**
     * Use this to put the Data on a LOADING Status
     */
    public void postLoading() {
        postValue(new StateData<T>().loading());
    }

    /**
     * Use this to put the Data on a ERROR DataStatus
     * @param throwable the error to be handled
     */
    public void postError(Throwable throwable) {
        postValue(new StateData<T>().error(throwable));
    }

    /**
     * Use this to put the Data on a SUCCESS DataStatus
     * @param data
     */
    public void postSuccess(T data) {
        postValue(new StateData<T>().success(data));
    }

    /**
     * Use this to put the Data on a COMPLETE DataStatus
     */
    public void postComplete() {
        postValue(new StateData<T>().complete());
    }

}

И вот как вы это используете

StateLiveData<List<Book>> bookListLiveData;
bookListLiveData.postLoading();
bookListLiveData.postSuccess(books);
bookListLiveData.postError(e);

И как это можно наблюдать:

private void observeBooks() {
        viewModel.getBookList().observe(this, this::handleBooks);
    }

    private void handleBooks(@NonNull StateData<List<Book>> books) {
      switch (stepIds.getStatus()) {
            case SUCCESS:
                List<Book> bookList = books.getData();
                //TODO: Do something with your book data
                break;
            case ERROR:
                Throwable e = books.getError();
                //TODO: Do something with your error
                break;
            case LOADING:
                //TODO: Do Loading stuff
                break;
            case COMPLETE:
                //TODO: Do complete stuff if necessary
                break;
        }
    }

Ответ 4

Другим подходом является использование MediatorLiveData, в котором будут использоваться источники LiveData другого типа. Это даст вам возможность разделения каждого события:

Например:

open class BaseViewModel : ViewModel() {
    private val errorLiveData: MutableLiveData<Throwable> = MutableLiveData()
    private val loadingStateLiveData: MutableLiveData<Int> = MutableLiveData()
    lateinit var errorObserver: Observer<Throwable>
    lateinit var loadingObserver: Observer<Int>
    fun <T> fromPublisher(publisher: Publisher<T>): MediatorLiveData<T> {
        val mainLiveData = MediatorLiveData<T>()
        mainLiveData.addSource(errorLiveData, errorObserver)
        mainLiveData.addSource(loadingStateLiveData, loadingObserver)
        publisher.subscribe(object : Subscriber<T> {

            override fun onSubscribe(s: Subscription) {
                s.request(java.lang.Long.MAX_VALUE)
                loadingStateLiveData.postValue(LoadingState.LOADING)
            }

            override fun onNext(t: T) {
                mainLiveData.postValue(t)
            }

            override fun onError(t: Throwable) {
                errorLiveData.postValue(t)
            }

            override fun onComplete() {
                loadingStateLiveData.postValue(LoadingState.NOT_LOADING)
            }
        })

        return mainLiveData 
    }

}

В этом примере загрузка и ошибка LiveData начнут наблюдаться, как только MediatorLiveData будут иметь активных наблюдателей.

Ответ 5

В моем приложении мне пришлось переводить RxJava Observables в LiveData. При этом мне, конечно, пришлось поддерживать состояние ошибки. Вот как я это сделал (Котлин)

class LiveDataResult<T>(val data: T?, val error: Throwable?)

class LiveObservableData<T>(private val observable: Observable<T>) : LiveData<LiveDataResult<T>>() {
    private var disposable = CompositeDisposable()

    override fun onActive() {
        super.onActive()

        disposable.add(observable.subscribe({
            postValue(LiveDataResult(it, null))
        }, {
            postValue(LiveDataResult(null, it))
        }))
    }

    override fun onInactive() {
        super.onInactive()

        disposable.clear()
    }
}

Ответ 6

Я создал приложение для поиска фильмов здесь, в котором я использовал разные объекты LiveData, один для успешного ответа из сети, а другой для неудачного:

private val resultListObservable = MutableLiveData<List<String>>()
private val resultListErrorObservable = MutableLiveData<HttpException>()

fun findAddress(address: String) {
    mainModel.fetchAddress(address)!!.subscribeOn(schedulersWrapper.io()).observeOn(schedulersWrapper.main()).subscribeWith(object : DisposableSingleObserver<List<MainModel.ResultEntity>?>() {
        override fun onSuccess(t: List<MainModel.ResultEntity>) {
            entityList = t
            resultListObservable.postValue(fetchItemTextFrom(t))
        }

        override fun onError(e: Throwable) {
            resultListErrorObservable.postValue(e as HttpException)
        }
    })
}