72

The new LiveData can be used as a replacement for RxJava's observables in some scenarios. However, unlike Observable, LiveData has no callback for errors.

My question is: How should I handle errors in LiveData, e.g. when it's backed by some network resource that can fail to be retrieved due to an IOException?

Kirill Rakhman
  • 35,458
  • 17
  • 110
  • 133

7 Answers7

58

In one of Google's sample apps for Android Architecture Components they wrap the LiveData emitted object in a class that can contain a status, data, and message for the emitted object.

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

With this approach you can use the status to determine if there was an error.

Dennis Lee
  • 60
  • 1
  • 1
  • 7
Chris Cook
  • 604
  • 7
  • 4
40

You can extend from MutableLiveData and create a holder Model to wrap your data.

This is your Wrapper Model

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

This is your extended LiveData Object

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

}

And this is how you use it

StateLiveData<List<Book>> bookListLiveData;
bookListLiveData.postLoading();
bookListLiveData.postSuccess(books);
bookListLiveData.postError(e);

And how it can be observed:

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;
        }
    }
Benjamin Mesing
  • 3,107
  • 1
  • 13
  • 18
Eliel Martinez
  • 401
  • 4
  • 2
19

Wrap the Data that you return from LiveData with some sort of error Messaging

public class DataWrapper<T>T{
    private T data;
    private ErrorObject error; //or A message String, Or whatever
}

//Now in your LifecycleRegistryOwner Class

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
    }

});

Just Catch an Exception instead or throwing it. use the error Object to pass this Data to the UI.

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

//On Exception catching:
liveData.set(new DataWrapper(null, new ErrorObject(e));
royB
  • 11,685
  • 14
  • 48
  • 77
  • One question, can we convert the `LiveData` into an observable `Observable>` ? Then we can handle the errors there ? – Santanu Sur Jan 13 '18 at 10:04
13

Another approach is to use MediatorLiveData that will take sources of LiveData of different type. This will give you separation of each event:

For example:

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 
    }

}

In this example loading and error LiveData will start being observed once the MediatorLiveData will have active observers.

Nikola Despotoski
  • 46,951
  • 13
  • 114
  • 146
  • I was looking specifically for this approach and I'm glad I found exactly this approach (using multiple LiveData and post into them via a MediatorLiveData). :+1: – EpicPandaForce May 07 '18 at 14:00
  • please note that Flowables can represent multiple elements though, in which case onComplete() is never called. – EpicPandaForce May 07 '18 at 14:02
  • 1
    @Nikola Despotoski, it's late but having question about what if the os kills activity and restores it, during restoring flow the `MediatorLiveData` will be observed again (it is still alive with in the viewModel), problem is when it is registered/observed the liveData will deliver what was post to the liveData last time. If the last post was an error state the restored activity will not be able to get the previously posted data so cannot resume the UI experience as of before os killing the activity. How to deal with the os killing/restoring the activity is using the `MediatorLiveData`? – lannyf Nov 03 '19 at 22:57
  • @lannyf Take a look at `SingleLiveData` it will avoid delivering latest result to the new observers. That's a one way to avoid it. – Nikola Despotoski Nov 04 '19 at 12:24
  • @Nikola Despotoski, thanks for the reply. But that does not resolve the issue that when os restores the activity and it does not get the previously posted `data` (if the the last post in liveData is `state` after the post of `data`). We could ignore the `state` in the liveData when register to the liveData, but how to get the the data in order to restore the previously UI experience? If we had two separate liveData channels one for `data` and one for `state` it would not have this problem, then how to combine them into one liveData? – lannyf Nov 04 '19 at 13:08
3

In my app, I had to translate RxJava Observables into LiveData. While doing that, I of course had to maintain the error state. Here's how I did it (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()
    }
}
dzikovskyy
  • 4,662
  • 3
  • 30
  • 42
  • 1
    That's cool, But how come you didn't use LiveDataReactiveStream? – Hossein Shahdoost Jan 23 '18 at 14:03
  • `LiveDataReactiveStreams.fromPublisher()` does not handle errors, as stated in the documentation. An Rx error will throw an error on the main thread and crash the app. However you can probably also wrap the errors in a `LiveDataResult` at the Rx level then use `LiveDataReactiveStreams.fromPublisher()` to transform it to a LiveData. – BladeCoder Jan 23 '18 at 17:42
1

Just some implementation of the method from Chris Cook's answer:

At first, we need the object that will contain response data and exceptions:

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

And then my own invention - AsyncExecutor.

This small class do 3 important things:

  1. Return standard convenient LiveData object.
  2. Call provided callback asynchronously.
  3. Takes the result of the callback or catch any exception and put it to the 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
        }
    }
}

Then you can create a LiveData in your ViewModel, contains the result of your callback or exception:


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

And then you can get your data and any exceptions in the 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
    }
}

James Bond
  • 718
  • 6
  • 12
0

I have built a movie search app here in which I have used to different LiveData objects, one for the successful response from the network and one for the unsuccessful:

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)
        }
    })
}
Ali Nem
  • 4,220
  • 1
  • 38
  • 36