web-dev-qa-db-fra.com

Comment gérer les états d'erreur avec LiveData?

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?

50
Kirill Rakhman

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.

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

Avec cette approche, vous pouvez utiliser le statut pour déterminer s'il y a une erreur.

41
Chris Cook

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));
15
royB

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;
        }
    }
11
Eliel Martinez

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.

9
Nikola Despotoski

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

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)
        }
    })
}
0
Ali Nem