package com.futurice.android.rx.util;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.Subscription;
/**
* If the {@link rx.Observer} is garbage collected, the subscription is canceled
* <p/>
* It can optionally unsubscribeSource the subscription on first notification. This default is stay subscribed.
* <p/>
* It can optionally unsubscribeSource any previous singleton subscriptions to ensure only one observer
* of a given class is active at any time to avoid multiple refire issues in transition
*
* @param <T>
*/
public class WeakObserver<T> {
private static final HashMap<Class, WeakObserver> singletonWeakObserverMap = new HashMap<>();
public static <T> Subscription observe(Observable<T> observable, Observer<T> observer) {
return observe(observable, observer, false);
}
public static <T> Subscription observe(Observable<T> observable, Observer<T> observer, boolean unsubscribeOnError) {
return doObserve(observable, observer, unsubscribeOnError).subscription;
}
private static <T> WeakObserver<T> doObserve(Observable<T> observable, Observer<T> observer, boolean unsubscribeOnError) {
return new WeakObserver<T>(observable, observer, unsubscribeOnError);
}
public static <T> Subscription singletonObserve(Observable<T> observable, Observer<T> observer) {
return doSingletonObserve(observable, observer, false).subscription;
}
public static <T> Subscription singletonObserve(Observable<T> observable, Observer<T> observer, boolean unsubscribeOnError) {
return doSingletonObserve(observable, observer, unsubscribeOnError).subscription;
}
private static synchronized <T> WeakObserver<T> doSingletonObserve(Observable<T> observable, Observer<T> observer, boolean unsubscribeOnError) {
WeakObserver oldWeakObserver = singletonWeakObserverMap.remove(observable.getClass());
if (oldWeakObserver != null) {
oldWeakObserver.subscription.unsubscribe();
// There exists this brief interval during which neither singletons is subscribed
// This is intentional to avoid the alternative, which is both singletons are subscribed split respond to events
// On new subscription, the default is Rx sends the most recent getValue, so this gap is illusory unless you change this behaviour
}
WeakObserver newWeakObserver = doObserve(observable, observer, unsubscribeOnError);
singletonWeakObserverMap.put(observable.getClass(), newWeakObserver);
return newWeakObserver;
}
final Subscription subscription;
final WeakReference<Observer<T>> weakRefObserver;
final Class observerClass;
private WeakObserver(final Observable<T> observable, Observer<T> observer, final boolean unsubscribeOnError) {
weakRefObserver = new WeakReference<>(observer);
observerClass = observer.getClass();
subscription = observable.subscribe(new Subscriber<T>() {
@Override
public void onCompleted() {
subscription.unsubscribe();
WeakObserver.remove(observerClass, WeakObserver.this);
Observer obs = dereference();
if (obs != null) {
obs.onCompleted();
}
}
@Override
public void onError(Exception e) {
if (unsubscribeOnError) {
subscription.unsubscribe();
WeakObserver.remove(observerClass, WeakObserver.this);
}
Observer obs = dereference();
if (obs != null) {
obs.onError(e);
}
}
@Override
public void onNext(T t) {
Observer obs = dereference();
if (obs != null) {
obs.onNext(t);
}
}
});
}
private static synchronized void remove(Class c, WeakObserver weakObserver) {
if (singletonWeakObserverMap.get(c).equals(weakObserver)) {
singletonWeakObserverMap.remove(c);
}
}
private Observer<T> dereference() {
Observer<T> observer = weakRefObserver.get();
if (observer == null) {
subscription.unsubscribe();
}
return observer;
}
}