Как обрабатывать состояния ошибок с помощью LiveData?
Новый LiveData
можно использовать в качестве замены для наблюдаемых RxJava в некоторых сценариях. Однако, в отличие от Observable
, LiveData
не имеет обратного вызова для ошибок.
Мой вопрос: как я должен обрабатывать ошибки в LiveData
, например. когда он поддерживается каким-то сетевым ресурсом, который может не получить из-за IOException
?
Ответы
Ответ 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)
}
})
}