如何使用 LiveData 处理错误状态?
Posted
技术标签:
【中文标题】如何使用 LiveData 处理错误状态?【英文标题】:How to handle error states with LiveData? 【发布时间】:2017-10-27 18:19:20 【问题描述】:在某些场景下,新的LiveData
可以用作 RxJava 的 observables 的替代品。但是,与Observable
不同,LiveData
没有错误回调。
我的问题是:我应该如何处理LiveData
中的错误,例如当它由某个网络资源支持时,由于IOException
而无法检索?
【问题讨论】:
我认为***.com/a/45880925/2413303 是这里最干净的变体。 难道你不能在 ViewModel 中添加一个可为空的 errorCallback 变量,如果它不为空就调用它吗?这样,“订阅”和“取消订阅”仍然是片段/活动作业。它不使用 LiveData,但我认为它仍然可以工作。 【参考方案1】:您可以从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 (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;
【讨论】:
当我们有很多StateLiveData的时候,我们必须有很多handbooks!! 我无法从StateLiveData
类中转换LiveData
switch中的stepIds是什么?【参考方案2】:
克里斯库克回答中的方法的一些实现:
首先,我们需要包含响应数据和异常的对象:
/**
* 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)
然后是我自己的发明 - AsyncExecutor。
这个小班做 3 件重要的事情:
-
返回标准方便的 LiveData 对象。
异步调用提供的回调。
获取回调结果或捕获任何异常并将其放入 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
【讨论】:
【参考方案3】:我已经构建了一个电影搜索应用程序here,我在其中使用了不同的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)
)
【讨论】:
这样做你需要从 UI 中附加 2 个观察者【参考方案4】:在 Google 的 sample apps for Android Architecture Components 之一中,他们将 LiveData 发射对象包装在一个类中,该类可以包含发射对象的状态、数据和消息。
https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.kt
通过这种方法,您可以使用状态来确定是否有错误。
【讨论】:
示例在 kotlin 中..在 Java 中有吗? 注意:这不是在 Room 的 Livedata 支持中完成的。数据库查询中未处理的异常将使整个应用程序崩溃。 如何将这种方法与 DataBinding 结合起来?【参考方案5】:在我的应用程序中,我必须将 RxJava Observables 转换为 LiveData。在这样做的同时,我当然必须保持错误状态。这是我的做法(Kotlin)
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()
【讨论】:
这很酷,但是你为什么不使用 LiveDataReactiveStream?LiveDataReactiveStreams.fromPublisher()
不处理错误,如文档中所述。 Rx 错误将在主线程上引发错误并使应用程序崩溃。但是,您也可以在 Rx 级别将错误包装在 LiveDataResult
中,然后使用 LiveDataReactiveStreams.fromPublisher()
将其转换为 LiveData。【参考方案6】:
另一种方法是使用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 并通过 MediatorLiveData 发布到它们中)。 :+1: 请注意,Flowables 可以表示多个元素,在这种情况下永远不会调用 onComplete()。 @Nikola Despotoski,已经很晚了,但是如果操作系统杀死活动并恢复它会怎样,在恢复流程期间,MediatorLiveData
将再次被观察到(它在 viewModel 中仍然存在),问题是当它被注册/观察时,liveData 将传递上次发布到 liveData 的内容。如果最后一个帖子是错误状态,则恢复的活动将无法获取先前发布的数据,因此无法恢复 UI 体验,因为在操作系统终止活动之前。如何处理正在使用MediatorLiveData
的操作系统杀死/恢复活动?
@lannyf 看看SingleLiveData
,它将避免向新观察者提供最新结果。这是避免它的一种方法。
@Nikola Despotoski,感谢您的回复。但这并不能解决当 os 恢复活动并且它没有得到之前发布的data
的问题(如果 liveData 中的最后一个帖子是state
在data
的帖子之后)。注册liveData时可以忽略liveData中的state
,但是如何获取数据才能恢复之前的UI体验呢?如果我们有两个独立的 liveData 通道,一个用于data
,一个用于state
,就不会出现这个问题,那么如何将它们组合成一个 liveData?【参考方案7】:
使用某种错误消息包装从 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
或将其扔掉即可。使用错误对象将此数据传递给 UI。
MutableLiveData<DataWrapper<SomObject>> liveData = new...;
//On Exception catching:
liveData.set(new DataWrapper(null, new ErrorObject(e));
【讨论】:
一个问题,我们可以将LiveData
转换为可观察的Observable<LiveData<Model>>
吗?然后我们可以处理那里的错误?以上是关于如何使用 LiveData 处理错误状态?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 React Apollo 客户端处理 HTTP 状态错误