如何使用 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 中的最后一个帖子是statedata 的帖子之后)。注册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&lt;LiveData&lt;Model&gt;&gt; 吗?然后我们可以处理那里的错误?

以上是关于如何使用 LiveData 处理错误状态?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 React Apollo 客户端处理 HTTP 状态错误

如何使用 PHP 处理 403 错误

将LiveData转换为MutableLiveData

17springboot——CRUD-错误处理机制⑧

将 LiveData 转换为 MutableLiveData

Swift 结合处理 HTTP 状态码错误