78 votes

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

Le nouveau LiveData peut être utilisé pour remplacer les observables de RxJava dans certains scénarios. Cependant, contrairement à Observable , LiveData n'a pas de rappel pour les erreurs.

Ma question est la suivante : comment dois-je traiter les erreurs dans LiveData par exemple, lorsqu'il est soutenu par une ressource réseau qui peut ne pas être récupérée en raison d'une défaillance du réseau. IOException ?

1 votes

Je pense stackoverflow.com/a/45880925/2413303 est la variante la plus propre ici.

0 votes

Ne pouvez-vous pas simplement ajouter une variable nullable errorCallback au ViewModel et l'appeler si elle n'est pas nulle ? De cette façon, ce serait toujours au Fragment/Activités de "s'abonner" et "se désabonner". Il n'utilise pas LiveData mais je pense que cela devrait quand même fonctionner.

58voto

Chris Cook Points 404

Dans l'un des programmes de Google exemples d'applications pour les composants d'architecture Android ils enveloppent l'objet LiveData émis dans une classe qui peut contenir un état, 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 eu une erreur.

6 votes

L'échantillon était en Kotlin Et en Java ?

1 votes

Méfiez-vous : Ceci n'a pas été fait dans le support Livedata pour Room. Les exceptions non gérées de la requête de la base de données feront planter l'application entière.

1 votes

Comment combiner cette approche avec DataBinding ?

45voto

Eliel Martinez Points 41

Vous pouvez étendre de MutableLiveData et créer un modèle de détenteur pour envelopper vos données.

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

C'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 voici comment l'utiliser

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 (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 votes

Lorsque nous avons beaucoup de StateLiveData, nous devons avoir beaucoup de handlebooks !

2 votes

Je ne suis pas capable de lancer le LiveData de StateLiveData classe

0 votes

Que sont les stepIds dans le commutateur ?

19voto

royB Points 5787

Enveloppez les données que vous retournez de LiveData avec une sorte de message d'erreur.

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

//Maintenant dans votre LifecycleRegistryOwner Classe

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
    }

});

Attrapez juste un Exception au lieu de la lancer. Utilisez l'objet d'erreur pour passer ces données à l'interface utilisateur.

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

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

0 votes

Une question, peut-on convertir le LiveData en une observable Observable<LiveData<Model>> ? Nous pouvons alors traiter les erreurs à cet endroit ?

14voto

Nikola Despotoski Points 13670

Une autre approche consiste à utiliser MediatorLiveData qui prendra les sources de LiveData de type différent. Cela vous permettra de séparer 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 commencera à être observé dès que le MediatorLiveData aura des observateurs actifs.

0 votes

Je cherchais spécifiquement cette approche et je suis heureux d'avoir trouvé exactement cette approche (utiliser plusieurs LiveData et y poster via un MediatorLiveData) :+1 :

0 votes

Veuillez noter que les Flowables peuvent toutefois représenter plusieurs éléments, auquel cas onComplete() n'est jamais appelé.

1 votes

@Nikola Despotoski, il est tard mais j'ai une question à propos de ce qui se passe si le système d'exploitation tue l'activité et la restaure, pendant le rétablissement du flux, l'activité de l'utilisateur est supprimée. MediatorLiveData sera à nouveau observée (elle est toujours vivante dans le viewModel), le problème est que lorsqu'elle sera enregistrée/observée, la liveData délivrera ce qui a été posté à la liveData la dernière fois. Si le dernier message était une erreur, l'activité restaurée ne sera pas en mesure d'obtenir les données précédemment affichées et ne pourra donc pas reprendre l'expérience de l'interface utilisateur comme avant que l'os ne tue l'activité. La façon de gérer la destruction/restauration de l'activité par l'OS est d'utiliser la méthode de récupération des données. MediatorLiveData ?

3voto

Dans mon application, j'ai dû traduire les Observables de RxJava en LiveData. En faisant cela, 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()
    }
}

1 votes

C'est cool, mais comment se fait-il que vous n'ayez pas utilisé LiveDataReactiveStream ?

0 votes

LiveDataReactiveStreams.fromPublisher() ne gère pas les erreurs, comme indiqué dans la documentation. Une erreur Rx déclenchera une erreur sur le thread principal et fera planter l'application. Cependant, vous pouvez probablement aussi envelopper les erreurs dans un fichier LiveDataResult au niveau de la Rx, puis utiliser LiveDataReactiveStreams.fromPublisher() pour le transformer en LiveData.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X