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

How to handle error states with LiveData?

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

我的问题是:我应该如何处理 LiveData 中的错误,例如当它受到某些网络资源的支持时,由于 IOException?

可能无法检索

在 Google 的 sample apps for Android Architecture Components 之一中,他们将 LiveData 发射对象包装在 class 中,其中可以包含发射对象的状态、数据和消息。

https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.kt

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

在我的应用程序中,我必须将 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()
    }
}

使用某种错误消息

从 LiveData 包装您 return 的数据
public class DataWrapper<T>T{
    private T data;
    private ErrorObject error; //or A message String, Or whatever
}

//现在在你的LifecycleRegistryOwnerClass

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 
    }

}

在此示例中,加载和错误 LiveData 将在 MediatorLiveData 有活跃的观察者后开始观察。

我构建了一个电影搜索应用程序 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)
        }
    })
}

您可以从 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;
        }
    }

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

然后是我自己的发明——AsyncExecutor.

这个小 class 做了 3 件重要的事情:

  1. Return 标准方便的 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
    }
}