Das neue LiveData
kann in einigen Szenarien als Ersatz für die Observablen von RxJava verwendet werden. Doch im Gegensatz zu Observable
, LiveData
hat keinen Rückruf für Fehler.
Meine Frage lautet: Wie soll ich mit Fehlern umgehen LiveData
, z. B. wenn sie von einer Netzwerkressource unterstützt werden, die aufgrund einer nicht abgerufen werden kann IOException
?
android
android-architecture-components
Kirill Rakhman
quelle
quelle
Antworten:
In einer der Beispiel-Apps von Google für Android-Architekturkomponenten wird das von LiveData ausgegebene Objekt in eine Klasse eingeschlossen, die einen Status, Daten und eine Nachricht für das ausgegebene Objekt enthalten kann.
https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.kt
Mit diesem Ansatz können Sie anhand des Status feststellen, ob ein Fehler aufgetreten ist.
quelle
Sie können
MutableLiveData
ein Haltermodell erweitern und erstellen, um Ihre Daten zu verpacken.Dies ist Ihr Wrapper-Modell
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 } }
Dies ist Ihr erweitertes LiveData-Objekt
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()); } }
Und so benutzt du es
StateLiveData<List<Book>> bookListLiveData; bookListLiveData.postLoading(); bookListLiveData.postSuccess(books); bookListLiveData.postError(e);
Und wie es beobachtet werden kann:
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; } }
quelle
LiveData
aus derStateLiveData
Klasse zuSchließen Sie die von LiveData zurückgegebenen Daten mit einer Art Fehlermeldung ab
public class DataWrapper<T>T{ private T data; private ErrorObject error; //or A message String, Or whatever }
// Jetzt in deiner
LifecycleRegistryOwner
KlasseLiveData<DataWrapper<SomeObjectClass>> result = modelView.getResult(); result.observe(this, newData ->{ if(newData.error != null){ //Can also have a Status Enum //Handle Error } else{ //Handle data } });
Fangen Sie
Exception
stattdessen eine oder werfen Sie sie. Verwenden Sie das Fehlerobjekt, um diese Daten an die Benutzeroberfläche zu übergeben.MutableLiveData<DataWrapper<SomObject>> liveData = new...; //On Exception catching: liveData.set(new DataWrapper(null, new ErrorObject(e));
quelle
LiveData
in ein beobachtbares umwandelnObservable<LiveData<Model>>
? Dann können wir die Fehler dort behandeln?Ein anderer Ansatz ist die Verwendung von
MediatorLiveData
QuellenLiveData
unterschiedlichen Typs. Dies gibt Ihnen eine Trennung von jedem Ereignis:Zum Beispiel:
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 diesem Beispiel werden Laden und Fehler
LiveData
beobachtet, sobaldMediatorLiveData
aktive Beobachter vorhanden sind.quelle
MediatorLiveData
wird das erneut beobachtet (es ist im viewModel noch aktiv). Das Problem ist, wenn es registriert / beobachtet wird Liefern Sie das, was beim letzten Mal in den liveData gepostet wurde. Wenn der letzte Beitrag ein Fehlerstatus war, kann die wiederhergestellte Aktivität die zuvor veröffentlichten Daten nicht abrufen, sodass die Benutzeroberflächenerfahrung nicht vor dem Beenden der Aktivität fortgesetzt werden kann. Wie gehe ich mit dem Betriebssystem um, das die Aktivität tötet / wiederherstelltMediatorLiveData
?SingleLiveData
darauf, um zu vermeiden, dass die neuen Beobachter das neueste Ergebnis erhalten. Das ist eine Möglichkeit, dies zu vermeiden.data
behebt jedoch nicht das Problem, dass wenn os die Aktivität wiederherstellt und die zuvor veröffentlichten nicht abgerufen werden (wenn der letzte Beitrag in liveDatastate
nach dem Beitrag von liegtdata
). Wir könnten diestate
in den liveData ignorieren, wenn wir uns bei den liveData registrieren, aber wie können wir die Daten abrufen, um die vorherige UI-Erfahrung wiederherzustellen? Wenn wir zwei separate liveData-Kanäle hätten, einen fürdata
und einen fürstate
, würde dieses Problem nicht auftreten. Wie können sie dann zu einem liveData kombiniert werden?In meiner App musste ich RxJava Observables in LiveData übersetzen. Dabei musste ich natürlich den Fehlerzustand beibehalten. So habe ich es gemacht (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() } }
quelle
LiveDataReactiveStreams.fromPublisher()
behandelt keine Fehler, wie in der Dokumentation angegeben. Ein Rx-Fehler löst einen Fehler im Haupt-Thread aus und stürzt die App ab. Sie können die Fehler jedoch wahrscheinlich auch auf Empfangsebene in a einschließen undLiveDataResult
dann verwenden,LiveDataReactiveStreams.fromPublisher()
um sie in LiveData umzuwandeln.Nur eine Implementierung der Methode aus Chris Cooks Antwort:
Zuerst benötigen wir das Objekt, das Antwortdaten und Ausnahmen enthält:
/** * 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) } } }
Und dann meine eigene Erfindung - AsyncExecutor .
Diese kleine Klasse macht 3 wichtige Dinge:
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 } } }
Anschließend können Sie in Ihrem ViewModel LiveData erstellen, die das Ergebnis Ihres Rückrufs oder Ihrer Ausnahme enthalten:
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() } } }
Und dann können Sie Ihre Daten und alle Ausnahmen in der Benutzeroberfläche abrufen:
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 } }
quelle
Ich habe hier eine Filmsuch-App erstellt , in der ich verschiedene
LiveData
Objekte verwendet habe, eines für die erfolgreiche Antwort aus dem Netzwerk und eines für die erfolglose: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) } }) }
quelle