如何使用 LiveData 处理错误状态?

在某些场景中,新的 LiveData可以替代 RxJava 的可观测数据。但是,与 Observable不同,LiveData没有对错误的回调。

我的问题是: 我应该如何处理 LiveData中的错误,例如,当它由一些网络资源支持,而这些资源由于 IOException而无法检索时?

39389 次浏览

在 Google 的 Android 架构组件示例应用程序中,他们将 LiveData 发出的对象包装在一个类中,该类可以包含发出对象的状态、数据和消息。

Https://github.com/googlesamples/android-architecture-components/blob/master/githubbrowsersample/app/src/main/java/com/android/example/github/vo/resource.kt

使用这种方法,您可以使用状态来确定是否存在错误。

在我的应用程序中,我必须将 RxJava 观察数据转换成 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()
}
}

用某种错误消息包装从 LiveData 返回的 Data

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或抛出它。使用错误对象传递这个数据到 UI。

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


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

另一种方法是使用 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
}


}

在这个例子中,一旦 MediatorLiveData拥有活动的观察者,就会开始观察加载和错误 LiveData

我已经建立了一个电影搜索应用程序 给你,其中我已经使用了不同的 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)
}
})
}

您可以从 MutableLiveData扩展并创建一个包装数据的 holder Model。

这是你的包装模型

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 (books.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;
}
}

这个方法的一些实现来自 Chris Cook 的回答:

首先,我们需要包含响应数据和异常的对象:

/**
* A generic class that holds a value with its loading status.
*
* @see <a href="https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.kt">Sample apps for Android Architecture Components</a>
*/
data class Resource<out T>(val status: Status, val data: T?, val exception: Throwable?) {
enum class Status {
LOADING,
SUCCESS,
ERROR,
}


companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}


fun <T> error(exception: Throwable): Resource<T> {
return Resource(Status.ERROR, null, exception)
}


fun <T> loading(): Resource<T> {
return Resource(Status.LOADING, null, null)
}
}
}


然后是我自己的发明 -异步执行器

这个小班做三件重要的事情:

  1. 返回方便的标准 LiveData 对象。
  2. 异步调用提供的回调。
  3. 获取回调或捕获任何异常的结果并将其放入 LiveData。

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData


class AsyncExecutor {
companion object {
fun <T> run(callback: () -> T): LiveData<Resource<T>> {
val resourceData: MutableLiveData<Resource<T>> = MutableLiveData()


Thread(Runnable {
try {
resourceData.postValue(Resource.loading())
val callResult: T = callback()
resourceData.postValue(Resource.success(callResult))
} catch (e: Throwable) {
resourceData.postValue(Resource.error(e))
}
}).start()


return resourceData
}
}
}


然后,您可以在 ViewModel 中创建一个 LiveData,其中包含回调或异常的结果:


class GalleryViewModel : ViewModel() {
val myData: LiveData<Resource<MyData>>


init {
myData = AsyncExecutor.run {
// here you can do your synchronous operation and just throw any exceptions
return MyData()
}
}
}


然后你可以得到你的数据和 UI 中的任何异常:


class GalleryFragment : Fragment() {


override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
galleryViewModel = ViewModelProviders.of(this).get(GalleryViewModel::class.java)
       

// ...


// Subscribe to the data:
galleryViewModel.myData.observe(viewLifecycleOwner, Observer {
when {
it.status === Resource.Status.LOADING -> {
println("Data is loading...")
}
it.status === Resource.Status.ERROR -> {
it.exception!!.printStackTrace()
}
it.status === Resource.Status.SUCCESS -> {
println("Data has been received: " + it.data!!.someField)
}
}
})


return root
}
}