【问题标题】:How to handle error states with LiveData?如何使用 LiveData 处理错误状态?
【发布时间】:2017-10-27 18:19:20
【问题描述】:

在某些场景下,新的LiveData 可以用作 RxJava 的 observables 的替代品。但是,与Observable 不同,LiveData 没有错误回调。

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

【问题讨论】:

  • 我认为stackoverflow.com/a/45880925/2413303 是这里最干净的变体。
  • 难道你不能在 ViewModel 中添加一个可为空的 errorCallback 变量,如果它不为空就调用它吗?这样,“订阅”和“取消订阅”仍然是片段/活动作业。它不使用 LiveData,但我认为它仍然可以工作。

标签: android android-architecture-components


【解决方案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 件重要的事情:

  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
    }
}

【讨论】:

    【解决方案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; 吗?然后我们可以处理那里的错误?
    猜你喜欢
    • 1970-01-01
    • 2018-12-21
    • 2021-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多