Le nouveau LiveData
peut remplacer les observables de RxJava dans certains scénarios. Cependant, contrairement à Observable
, LiveData
ne dispose d'aucun rappel pour les erreurs.
Ma question est la suivante: comment gérer les erreurs dans LiveData
, par exemple quand il est sauvegardé par une ressource réseau qui peut ne pas être récupérée à cause d'un IOException
?
Dans l'une des exemples d'applications pour Android Composants d'architecture) de Google, l'objet enchaîné LiveData est encapsulé dans une classe pouvant contenir un statut, des données et un message pour l'objet émis.
Avec cette approche, vous pouvez utiliser le statut pour déterminer s'il y a une erreur.
Enveloppez les données que vous renvoyez de LiveData avec une sorte d'erreur
public class DataWrapper<T>T{
private T data;
private ErrorObject error; //or A message String, Or whatever
}
// Maintenant dans votre classe 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
}
});
Il suffit de saisir un Exception
à la place ou de le lancer. utilisez l'objet d'erreur pour transmettre ces données à l'interface utilisateur.
MutableLiveData<DataWrapper<SomObject>> liveData = new...;
//On Exception catching:
liveData.set(new DataWrapper(null, new ErrorObject(e));
Vous pouvez étendre de MutableLiveData
et créer un modèle de titulaire pour envelopper vos données.
Ceci est votre modèle d'emballage
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
}
}
Ceci est votre objet LiveData étendu
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());
}
}
Et c'est comme ça que vous l'utilisez
StateLiveData<List<Book>> bookListLiveData;
bookListLiveData.postLoading();
bookListLiveData.postSuccess(books);
bookListLiveData.postError(e);
Et comment on peut l'observer:
private void observeBooks() {
viewModel.getBookList().observe(this, this::handleBooks);
}
private void handleBooks(@NonNull StateData<List<Book>> books) {
switch (stepIds.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;
}
}
Une autre approche consiste à utiliser MediatorLiveData
qui utilisera des sources de LiveData
de types différents. Cela vous donnera la séparation de chaque événement:
Par exemple:
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
}
}
Dans cet exemple, le chargement et l'erreur LiveData
seront observés dès que le MediatorLiveData
aura des observateurs actifs.
Dans mon application, je devais traduire RxJava Observables en LiveData. Ce faisant, je devais bien sûr maintenir l'état d'erreur. Voici comment je l'ai fait (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()
}
}
J'ai construit une application de recherche de film ici dans laquelle j'ai déjà utilisé différents objets LiveData
, un pour la réponse positive du réseau et un pour l'échec:
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)
}
})
}