How to handle error states with LiveData?
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.
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;
}
}