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


72

LiveData在某些情况下,新功能可以替代RxJava的可观察对象。但是,与不同ObservableLiveData它没有回调错误。

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


我认为stackoverflow.com/a/45880925/2413303是这里最干净的变体。
EpicPandaForce

Answers:


58

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

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

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


4
样本在kotlin中。在Java中有人吗?
sudha

1
注意:在LiveData对Room的支持中并未完成此操作。数据库查询中未处理的异常将使整个应用程序崩溃。
Marcus Wolschon

您如何将这种方法与DataBinding结合在一起?
user123456789 '20

39

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

1
当我们有很多StateLiveData时,我们必须有很多手册!!
阿里·雷扎伊扬

2
我不能投了LiveDataStateLiveData
卡尔蒂克Garasia

交换机中的stepIds是什么?
Waqar Vicky'5

18

用某种错误消息包装从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代替或抛出它。使用错误的Object将此数据传递给UI。

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

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

一个问题,我们可以将其LiveData转换为可观察的Observable<LiveData<Model>>吗?那我们可以在那里处理错误吗?
Santanu Sur

13

另一种方法是使用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有活动的观察者,就会开始观察到装载和错误。


我一直在专门寻找这种方法,但很高兴我找到了这种方法(使用多个LiveData并通过MediatorLiveData将其发布到其中)。:+1:
EpicPandaForce

请注意,虽然Flowable可以表示多个元素,在这种情况下,永远不会调用onComplete()。
EpicPandaForce

1
@Nikola Despotoski,已经晚了,但是对操作系统是否杀死活动并恢复活动有疑问,在恢复流程期间MediatorLiveData将再次观察到它(在viewModel中仍然存在),问题是何时注册/观察到liveData将将上次发布到liveData的内容交付。如果最后一个发布是错误状态,则还原的活动将无法获取先前发布的数据,因此无法在终止该活动之前恢复UI体验。如何使用来处理os杀死/恢复活动MediatorLiveData
lannyf '19

@lannyf看一下SingleLiveData它将避免向新观察者传递最新结果。这是避免这种情况的一种方法。
Nikola Despotoski

@Nikola Despotoski,感谢您的回复。但这不能解决以下问题:当os恢复活动时,它不会获得先前发布的内容data(如果liveData中的最后发布内容位于state的发布之后data)。state注册到liveData时,我们可以忽略liveData中的,但是如何获取数据以恢复以前的UI体验?如果我们有两个单独的liveData通道,一个通道用于data一个通道,state则不会出现此问题,那么如何将它们合并为一个liveData通道?
lannyf

3

在我的应用程序中,我不得不将RxJava Observables转换为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()
    }
}

1
太酷了,但是您为什么不使用LiveDataReactiveStream?
Hossein Shahdoost

LiveDataReactiveStreams.fromPublisher()如文档中所述,不处理错误。Rx错误将在主线程上引发错误并使应用程序崩溃。但是,您也可以将错误包装LiveDataResult在Rx级别,然后用于LiveDataReactiveStreams.fromPublisher()将其转换为LiveData。
BladeCoder

1

只是克里斯·库克(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

这个小班做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()
        }
    }
}

然后,您可以在用户界面中获取数据和任何异常:


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


0

我在这里构建了一个电影搜索应用程序,其中我习惯了不同的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个观察员
加斯顿Saillén
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.