我目前正在尝试使用改造和Okhttp的API请求在架构组件中实现新的ViewModel,一切正常但我无法弄清楚如何将改进的错误响应传递给{{1}然后上游到片段中的观察者。这就是我到目前为止所做的:
LiveDataReactiveStreams.fromPublisher
在片段中,我在OnCreate中使用以下内容设置viewModel:
public class ShowListViewModel extends AndroidViewModel {
private final ClientAdapter clientAdapter;
private LiveData<List<Show>> shows;
public ShowListViewModel(Application application) {
super(application);
clientAdapter = new ClientAdapter(getApplication().getApplicationContext());
loadShows();
}
public LiveData<List<Show>> getShows() {
if (shows == null) {
shows = new MutableLiveData<>();
}
return shows;
}
void loadShows() {
shows = LiveDataReactiveStreams.fromPublisher(Observable.fromIterable(ShowsUtil.loadsIds())
.subscribeOn(Schedulers.io())
.flatMap(clientAdapter::getShowWithNextEpisode)
.observeOn(Schedulers.computation())
.toSortedList(new ShowsUtil.ShowComparator())
.observeOn(AndroidSchedulers.mainThread())
.toFlowable());
}
一切都按预期工作但目前如果我们离线,那么改造就会抛出一个runtimeException并崩溃。我认为问题在于:
ShowListViewModel model = ViewModelProviders.of(this).get(ShowListViewModel.class);
model.getShows().observe(this, shows -> {
if (shows == null || shows.isEmpty()) {
//This is where we may have empty list etc....
} else {
//process results from shows list here
}
});
通常我们会使用rxjava2 subscribe并从那里获取改进的错误,但是当使用LiveDataReactiveStreams.fromPublisher时,它为我们订阅了flowable。那么我们如何将此错误传递到此处:
LiveDataReactiveStreams.fromPublisher(Observable.fromIterable(ShowsUtil.loadsIds())
.subscribeOn(Schedulers.io())
.flatMap(clientAdapter::getShowWithNextEpisode)
.observeOn(Schedulers.computation())
.toSortedList(new ShowsUtil.ShowComparator())
.observeOn(AndroidSchedulers.mainThread())
.toFlowable());
}
答案 0 :(得分:1)
不仅需要通过LiveData对象公开展示列表,还需要将展示和错误包装到可以容纳错误的类中。
通过您的示例,您可以执行以下操作:
LiveDataReactiveStreams.fromPublisher(Observable.fromIterable(ShowsUtil.loadsIds())
.subscribeOn(Schedulers.io())
.flatMap(clientAdapter::getShowWithNextEpisode)
.observeOn(Schedulers.computation())
.toSortedList(new ShowsUtil.ShowComparator())
.observeOn(AndroidSchedulers.mainThread())
.map(Result::success)
.onErrorReturn(Result::error)
.toFlowable());
其中Result是包含错误或结果的包装器类
final class Result<T> {
private final T result;
private final Throwable error;
private Result(@Nullable T result, @Nullable Throwable error) {
this.result = result;
this.error = error;
}
@NonNull
public static <T> Result<T> success(@NonNull T result) {
return new Result(result, null);
}
@NonNull
public static <T> Result<T> error(@NonNull Throwable error) {
return new Result(null, error);
}
@Nullable
public T getResult() {
return result;
}
@Nullable
public Throwable getError() {
return error;
}
}
答案 1 :(得分:0)
我正在写一篇博客文章,内容涉及Kotlin中LiveData的错误处理。我的解决方案使用LiveDataReactiveStreams.fromPublisher
以及基于以下类的通用包装类Result。
该课程应该为您提供一些帮助。只要我准备好博客文章,就可以更新此答案。
答案 2 :(得分:0)
就我而言,我将LiveDataReactiveStreams.java复制为“ MyLiveDataReactiveStreams.java”到“ util”包中。修改后的版本将RuntimeException替换为GreenRobot EventBus帖子。然后,在我的应用中,我可以订阅该事件并适当地处理错误。对于此解决方案,我必须在3个地方添加'@SuppressLint(“ RestrictedApi”)'。我不确定Google是否允许Play Store应用使用该功能。此仓库包含一个完整的示例:https://github.com/LeeHounshell/Dogs 下面是相关代码:
// add GreenRobot to your app/build.gradle
def greenrobot_version = '3.2.0'
def timberkt_version = '1.5.1'
implementation "org.greenrobot:eventbus:$greenrobot_version"
implementation "com.github.ajalt:timberkt:$timberkt_version"
//--------------------------------------------------
// next put this in your Activity or Fragment to handle the error
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onRxErrorEvent(rxError_event: RxErrorEvent) {
// do your custom error handling here
Toast.makeText(activity, rxError_event.errorDescription, Toast.LENGTH_LONG).show()
}
//--------------------------------------------------
// create a new class in 'util' to post the error
import com.github.ajalt.timberkt.Timber
import org.greenrobot.eventbus.EventBus
class RxErrorEvent(val description: String) {
private val _tag = "LEE: <" + RxErrorEvent::class.java.simpleName + ">"
lateinit var errorDescription: String
init {
errorDescription = description
}
fun post() {
Timber.tag(_tag).e("post $errorDescription")
EventBus.getDefault().post(this)
}
}
//--------------------------------------------------
// and use MyLiveDataReactiveStreams (below) instead of LiveDataReactiveStreams
/*
* This is a modified version of androidx.lifecycle.LiveDataReactiveStreams
* The original LiveDataReactiveStreams object can't handle RxJava error conditions.
* Now errors are emitted as RxErrorEvent objects using the GreenRobot EventBus.
*/
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.executor.ArchTaskExecutor;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import com.github.ajalt.timberkt.Timber;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import java.util.concurrent.atomic.AtomicReference;
/**
* Adapts {@link LiveData} input and output to the ReactiveStreams spec.
*/
@SuppressWarnings("WeakerAccess")
public final class MyLiveDataReactiveStreams {
private final static String _tag = "LEE: <" + MyLiveDataReactiveStreams.class.getSimpleName() + ">";
private MyLiveDataReactiveStreams() {
}
/**
* Adapts the given {@link LiveData} stream to a ReactiveStreams {@link Publisher}.
*
* <p>
* By using a good publisher implementation such as RxJava 2.x Flowables, most consumers will
* be able to let the library deal with backpressure using operators and not need to worry about
* ever manually calling {@link Subscription#request}.
*
* <p>
* On subscription to the publisher, the observer will attach to the given {@link LiveData}.
* Once {@link Subscription#request} is called on the subscription object, an observer will be
* connected to the data stream. Calling request(Long.MAX_VALUE) is equivalent to creating an
* unbounded stream with no backpressure. If request with a finite count reaches 0, the observer
* will buffer the latest item and emit it to the subscriber when data is again requested. Any
* other items emitted during the time there was no backpressure requested will be dropped.
*/
@NonNull
public static <T> Publisher<T> toPublisher(
@NonNull LifecycleOwner lifecycle, @NonNull LiveData<T> liveData) {
return new MyLiveDataReactiveStreams.LiveDataPublisher<>(lifecycle, liveData);
}
private static final class LiveDataPublisher<T> implements Publisher<T> {
final LifecycleOwner mLifecycle;
final LiveData<T> mLiveData;
LiveDataPublisher(LifecycleOwner lifecycle, LiveData<T> liveData) {
this.mLifecycle = lifecycle;
this.mLiveData = liveData;
}
@Override
public void subscribe(Subscriber<? super T> subscriber) {
subscriber.onSubscribe(new MyLiveDataReactiveStreams.LiveDataPublisher.LiveDataSubscription<T>(subscriber, mLifecycle, mLiveData));
}
static final class LiveDataSubscription<T> implements Subscription, Observer<T> {
final Subscriber<? super T> mSubscriber;
final LifecycleOwner mLifecycle;
final LiveData<T> mLiveData;
volatile boolean mCanceled;
// used on main thread only
boolean mObserving;
long mRequested;
// used on main thread only
@Nullable
T mLatest;
LiveDataSubscription(final Subscriber<? super T> subscriber,
final LifecycleOwner lifecycle, final LiveData<T> liveData) {
this.mSubscriber = subscriber;
this.mLifecycle = lifecycle;
this.mLiveData = liveData;
}
@Override
public void onChanged(@Nullable T t) {
if (mCanceled) {
return;
}
if (mRequested > 0) {
mLatest = null;
mSubscriber.onNext(t);
if (mRequested != Long.MAX_VALUE) {
mRequested--;
}
} else {
mLatest = t;
}
}
@SuppressLint("RestrictedApi")
@Override
public void request(final long n) {
if (mCanceled) {
return;
}
ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
@Override
public void run() {
if (mCanceled) {
return;
}
if (n <= 0L) {
mCanceled = true;
if (mObserving) {
mLiveData.removeObserver(MyLiveDataReactiveStreams.LiveDataPublisher.LiveDataSubscription.this);
mObserving = false;
}
mLatest = null;
mSubscriber.onError(
new IllegalArgumentException("Non-positive request"));
return;
}
// Prevent overflowage.
mRequested = mRequested + n >= mRequested
? mRequested + n : Long.MAX_VALUE;
if (!mObserving) {
mObserving = true;
mLiveData.observe(mLifecycle, MyLiveDataReactiveStreams.LiveDataPublisher.LiveDataSubscription.this);
} else if (mLatest != null) {
onChanged(mLatest);
mLatest = null;
}
}
});
}
@SuppressLint("RestrictedApi")
@Override
public void cancel() {
if (mCanceled) {
return;
}
mCanceled = true;
ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
@Override
public void run() {
if (mObserving) {
mLiveData.removeObserver(MyLiveDataReactiveStreams.LiveDataPublisher.LiveDataSubscription.this);
mObserving = false;
}
mLatest = null;
}
});
}
}
}
/**
* Creates an observable {@link LiveData} stream from a ReactiveStreams {@link Publisher}}.
*
* <p>
* When the LiveData becomes active, it subscribes to the emissions from the Publisher.
*
* <p>
* When the LiveData becomes inactive, the subscription is cleared.
* LiveData holds the last value emitted by the Publisher when the LiveData was active.
* <p>
* Therefore, in the case of a hot RxJava Observable, when a new LiveData {@link Observer} is
* added, it will automatically notify with the last value held in LiveData,
* which might not be the last value emitted by the Publisher.
* <p>
* Note that LiveData does NOT handle errors and it expects that errors are treated as states
* in the data that's held. In case of an error being emitted by the publisher, an error will
* be propagated to the main thread and the app will crash.
*
* @param <T> The type of data hold by this instance.
*/
@NonNull
public static <T> LiveData<T> fromPublisher(@NonNull Publisher<T> publisher) {
return new MyLiveDataReactiveStreams.PublisherLiveData<>(publisher);
}
/**
* Defines a {@link LiveData} object that wraps a {@link Publisher}.
*
* <p>
* When the LiveData becomes active, it subscribes to the emissions from the Publisher.
*
* <p>
* When the LiveData becomes inactive, the subscription is cleared.
* LiveData holds the last value emitted by the Publisher when the LiveData was active.
* <p>
* Therefore, in the case of a hot RxJava Observable, when a new LiveData {@link Observer} is
* added, it will automatically notify with the last value held in LiveData,
* which might not be the last value emitted by the Publisher.
*
* <p>
* Note that LiveData does NOT handle errors and it expects that errors are treated as states
* in the data that's held. In case of an error being emitted by the publisher, an error will
* be propagated to the main thread and the app will crash.
*
* @param <T> The type of data hold by this instance.
*/
private static class PublisherLiveData<T> extends LiveData<T> {
private final Publisher<T> mPublisher;
final AtomicReference<MyLiveDataReactiveStreams.PublisherLiveData.LiveDataSubscriber> mSubscriber;
PublisherLiveData(@NonNull Publisher<T> publisher) {
mPublisher = publisher;
mSubscriber = new AtomicReference<>();
}
@Override
protected void onActive() {
super.onActive();
MyLiveDataReactiveStreams.PublisherLiveData.LiveDataSubscriber s = new MyLiveDataReactiveStreams.PublisherLiveData.LiveDataSubscriber();
mSubscriber.set(s);
mPublisher.subscribe(s);
}
@Override
protected void onInactive() {
super.onInactive();
MyLiveDataReactiveStreams.PublisherLiveData.LiveDataSubscriber s = mSubscriber.getAndSet(null);
if (s != null) {
s.cancelSubscription();
}
}
final class LiveDataSubscriber extends AtomicReference<Subscription>
implements Subscriber<T> {
@Override
public void onSubscribe(Subscription s) {
if (compareAndSet(null, s)) {
s.request(Long.MAX_VALUE);
} else {
s.cancel();
}
}
@Override
public void onNext(T item) {
postValue(item);
}
@SuppressLint("RestrictedApi")
@Override
public void onError(final Throwable ex) {
mSubscriber.compareAndSet(this, null);
ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
@Override
public void run() {
//NOTE: Errors are be handled upstream
Timber.tag(_tag).e("LiveData does not handle errors. Errors from publishers are handled upstream via EventBus. error: " + ex);
RxErrorEvent rx_event = new RxErrorEvent(ex.toString());
rx_event.post();
}
});
}
@Override
public void onComplete() {
mSubscriber.compareAndSet(this, null);
}
public void cancelSubscription() {
Subscription s = get();
if (s != null) {
s.cancel();
}
}
}
}
}