io.nextop.rx.RxLifecycleBinder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android Show documentation
Show all versions of android Show documentation
Nextop client for Android 10+
The newest version!
package io.nextop.rx;
import android.view.View;
import com.google.common.base.Objects;
import immutablecollections.ImSet;
import rx.*;
import rx.Observable;
import rx.Observer;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action2;
import rx.observers.Subscribers;
import rx.subscriptions.BooleanSubscription;
import rx.subscriptions.CompositeSubscription;
import javax.annotation.Nullable;
public interface RxLifecycleBinder extends Subscription {
void reset();
/** this version compares the current id to the given id.
* This is useful for recycler views where tearing down and
* setting up the same id is wasteful/visually jarring.
* if different, does a reset and returns true.
* if the same, does nothing and returns false. */
boolean reset(Object id);
/** Wraps the given observable in an observable bound to the lifecyle of a container.
* The wrapper allocates on subscribe and cleans up on unsubscribe.
* Unsubscription of all can be forced with {@link #reset}.
*
* This ensures:
* - the subscription is connected to the source when started
* - the subscription is dropped from the source when stopped
* (onComplete is not called, because the subscription will resume when restarted) */
Observable bind(Observable source);
void bind(Subscription sub);
/** Binds to an internal lifecycle start/stop. */
final class Lifted implements RxLifecycleBinder {
private final Scheduler scheduler;
@Nullable
private Object currentId = null;
private ImSet> binds = ImSet.empty();
private final CompositeSubscription subscriptions = new CompositeSubscription();
private boolean connected = false;
@Nullable
private View connectedView = null;
private boolean closed = false;
@Nullable
private Subscription cascadeSubscription = null;
@Nullable
private RxDebugger debugger;
public Lifted() {
this(RxDebugger.get());
}
public Lifted(@Nullable RxDebugger debugger) {
scheduler = AndroidSchedulers.mainThread();
this.debugger = debugger;
}
public void setDebugger(@Nullable RxDebugger debugger) {
if (null != this.debugger) {
// FIXME
// FIXME detach
}
this.debugger = debugger;
if (null != debugger) {
// FIXME
// FIXME attach
}
}
public boolean isClosed() {
return closed;
}
public void connect() {
connect(null);
}
public void connect(@Nullable View view) {
if (closed) {
throw new IllegalStateException();
}
if (!connected) {
connected = true;
connectedView = view;
for (Bind> bind : binds) {
bind.connect();
}
}
}
public void disconnect() {
if (closed) {
throw new IllegalStateException();
}
if (connected) {
connected = false;
connectedView = null;
for (Bind> bind : binds) {
bind.disconnect();
}
}
}
public void close() {
if (closed) {
throw new IllegalStateException();
}
closed = true;
removeCascadeUnsubscribe();
clear();
subscriptions.unsubscribe();
}
public void removeCascadeUnsubscribe() {
if (null != cascadeSubscription) {
cascadeSubscription.unsubscribe();
cascadeSubscription = null;
}
}
// replaces previous cascade parent
public void cascadeUnsubscribe(@Nullable RxLifecycleBinder parent) {
removeCascadeUnsubscribe();
if (null != parent) {
cascadeSubscription = parent.bind(MoreObservables.hanging()).doOnCompleted(new Action0() {
@Override
public void call() {
unsubscribe();
}
}).subscribe();
}
}
private void clear() {
ImSet> _binds = binds;
binds = ImSet.empty();
for (Bind bind : _binds) {
bind.close();
}
subscriptions.clear();
}
/////// Subscription ///////
@Override
public void unsubscribe() {
close();
}
@Override
public boolean isUnsubscribed() {
return closed;
}
/////// RxLifecycleBinder ///////
@Override
public void reset() {
if (closed) {
throw new IllegalStateException();
}
currentId = null;
clear();
}
@Override
public boolean reset(Object id) {
if (closed) {
throw new IllegalStateException();
}
if (!Objects.equal(currentId, id)) {
currentId = id;
clear();
return true;
} else {
return false;
}
}
@Override
public Observable bind(Observable source) {
if (closed) {
return Observable.empty();
}
Bind bind = new Bind(source.subscribeOn(scheduler));
binds = binds.adding(bind);
if (connected) {
bind.connect();
}
return bind.adapter;
}
@Override
public void bind(Subscription sub) {
if (closed) {
sub.unsubscribe();
} else {
subscriptions.add(sub);
}
}
private final class Bind {
private final Observable source;
final Observable adapter;
private boolean closed = false;
private boolean connected;
private ImSet subscribers = ImSet.empty();
Bind(Observable source) {
this.source = source;
adapter = Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super T> subscriber) {
add(subscriber);
}
});
}
final class Bridge {
final Subscriber super T> subscriber;
private @Nullable Subscription subscription = null;
// DEBUG STATISTICS
private int onNextCount = 0;
private int onCompletedCount = 0;
private int onErrorCount = 0;
@Nullable
private Notification mostRecentNotification = null;
private int failedNotificationCount = 0;
@Nullable
private Notification mostRecentFailedNotification = null;
@Nullable
private Throwable mostRecentFailedNotificationReason = null;
Bridge(Subscriber super T> subscriber) {
this.subscriber = subscriber;
}
void subscribe(Subscription subscription) {
unsubscribe();
if (subscriber.isUnsubscribed()) {
subscription.unsubscribe();
} else {
this.subscription = subscription;
if (null != debugger && debugger.isEnabled()) {
debugger.update(new RxDebugger.Stats(RxDebugger.Stats.F_CONNECTED, subscriber, connectedView, false, true,
onNextCount, onCompletedCount, onErrorCount, mostRecentNotification,
failedNotificationCount, mostRecentFailedNotification, mostRecentFailedNotificationReason));
}
}
}
void unsubscribe() {
if (null != subscription) {
subscription.unsubscribe();
subscription = null;
if (null != debugger && debugger.isEnabled()) {
debugger.update(new RxDebugger.Stats(RxDebugger.Stats.F_DISCONNECTED, subscriber, connectedView, false, false,
onNextCount, onCompletedCount, onErrorCount, mostRecentNotification,
failedNotificationCount, mostRecentFailedNotification, mostRecentFailedNotificationReason));
}
}
}
void close() {
unsubscribe();
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
subscriber.unsubscribe();
if (null != debugger && debugger.isEnabled()) {
onCompletedCount += 1;
debugger.update(new RxDebugger.Stats(RxDebugger.Stats.F_COMPLETED, subscriber, connectedView, true, false,
onNextCount, onCompletedCount, onErrorCount, mostRecentNotification,
failedNotificationCount, mostRecentFailedNotification, mostRecentFailedNotificationReason));
}
}
}
public Subscriber inSubscriber() {
// if in debug, the notification is routed through the debugger
// which may step/filter notifications
final Action2 debugDelivery = new Action2() {
@Override
public void call(Subscriber subscriber, Notification notification) {
if (!subscriber.isUnsubscribed()) {
mostRecentNotification = notification;
int flags = 0;
try {
notification.accept(subscriber);
switch (notification.getKind()) {
case OnNext:
onNextCount += 1;
flags |= RxDebugger.Stats.F_NEXT;
break;
case OnError:
onErrorCount += 1;
flags |= RxDebugger.Stats.F_ERROR;
break;
case OnCompleted:
onCompletedCount += 1;
flags |= RxDebugger.Stats.F_COMPLETED;
break;
default:
throw new IllegalArgumentException();
}
} catch (Throwable t) {
failedNotificationCount += 1;
mostRecentFailedNotification = notification;
mostRecentFailedNotificationReason = t;
flags |= RxDebugger.Stats.F_FAILED;
}
if (null != debugger && debugger.isEnabled()) {
debugger.update(new RxDebugger.Stats(flags, subscriber, connectedView, false, !subscription.isUnsubscribed(),
onNextCount, onCompletedCount, onErrorCount, mostRecentNotification,
failedNotificationCount, mostRecentFailedNotification, mostRecentFailedNotificationReason));
}
}
}
};
return Subscribers.from(new Observer() {
@Override
public void onNext(T t) {
if (null != debugger && debugger.isEnabled()) {
debugger.deliver(subscriber, Notification.createOnNext(t), debugDelivery);
} else if (!subscriber.isUnsubscribed()) {
subscriber.onNext(t);
}
}
@Override
public void onCompleted() {
if (null != debugger && debugger.isEnabled()) {
debugger.deliver(subscriber, Notification.createOnCompleted(), debugDelivery);
} else if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
}
@Override
public void onError(Throwable e) {
if (null != debugger && debugger.isEnabled()) {
debugger.deliver(subscriber, Notification.createOnError(e), debugDelivery);
} else if (!subscriber.isUnsubscribed()) {
subscriber.onError(e);
}
}
});
}
public Subscription outSubscription() {
return BooleanSubscription.create(new Action0() {
@Override
public void call() {
unsubscribe();
subscribers = subscribers.removing(Bridge.this);
}
});
}
};
private void add(final Subscriber super T> subscriber) {
Bridge bridge = new Bridge(subscriber);
subscribers = subscribers.adding(bridge);
subscriber.add(bridge.outSubscription());
connect(bridge);
}
void connect() {
if (closed) {
throw new IllegalStateException();
}
if (!connected) {
connected = true;
for (Bridge bridge : subscribers) {
connect(bridge);
}
}
}
void connect(Bridge bridge) {
if (connected && !bridge.subscriber.isUnsubscribed()) {
bridge.subscribe(source.subscribe(bridge.inSubscriber()));
}
}
void disconnect() {
if (closed) {
throw new IllegalStateException();
}
if (connected) {
connected = false;
for (Bridge bridge : subscribers) {
bridge.unsubscribe();
}
}
}
// implies disconnect
void close() {
if (closed) {
throw new IllegalStateException();
}
disconnect();
if (!closed) {
closed = true;
for (Bridge bridge : subscribers) {
bridge.close();
}
}
}
}
}
}