package com.thebluealliance.androidclient.firebase;
import com.firebase.client.ChildEventListener;
import com.firebase.client.DataSnapshot;
import com.firebase.client.FirebaseError;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import rx.Observable;
import rx.subjects.PublishSubject;
import rx.subjects.SerializedSubject;
import rx.subjects.Subject;
/**
* Wraps a Firebase {@link ChildEventListener} in an {@link Observable} buffer
* that can pause and resume event delivery as required.
* <p>
* A problem I found with the default means of implementing ChildEventListener is that events could
* potentially be delivered when a hosting {@link android.app.Fragment} was in a state without a
* view, for instance, if the fragment was being retained across a configuration change. This meant
* that we couldn't reflect any new events in our list of objects. This solves that by wrapping the
* target listener in a buffer of sorts. Ordinarilly, events are passed straight through to the
* target listener. However, when you call {@link #pauseDelivery()}, any
* future events are buffered in an internal list. When you call {@link #resumeDelivery()},
* the events received during the paused state are delivered to the listener in the order that they
* were received (this happens synchronously).
* <p>
* @author Nathan
* @author Phil
*/
public class ResumeableRxFirebase implements ChildEventListener {
/**
* Events we've buffered so that we can deliver later
*/
private List<ChildEvent> mEvents = new ArrayList<>();
/**
* {@link Observable} subject we can post to
*/
private Subject<ChildEvent, ChildEvent> mSubject;
private boolean isDeliveryPaused;
@Inject
public ResumeableRxFirebase() {
mSubject = new SerializedSubject<>(PublishSubject.create());
isDeliveryPaused = false;
}
/**
* Returns an {@link Observable} that acts like an "event bus" for {@link ChildEvent}s.
* You should use {@link Observable#buffer(int)} on the result, because after a pause and
* resume in this class, the buffered events will come through very quickly (probably too
* quickly for your subscriber)
* @return an {@link Observable} that you can subscribe to
*/
public Observable<ChildEvent> getObservable() {
return mSubject;
}
/**
* This method stop emitting items to the observable, but buffers them internally
*/
public void pauseDelivery() {
this.isDeliveryPaused = true;
}
public void resumeDelivery() {
this.isDeliveryPaused = false;
for (int i = 0; i < mEvents.size(); i++) {
mSubject.onNext(mEvents.get(i));
}
}
/**
* Completes the observable
*/
public void finishDelivery() {
mSubject.onCompleted();
}
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
ChildEvent event = new ChildEvent(FirebaseChildType.CHILD_ADDED, dataSnapshot, s, null);
if (isDeliveryPaused) {
mEvents.add(event);
} else {
mSubject.onNext(event);
}
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
ChildEvent event = new ChildEvent(FirebaseChildType.CHILD_CHANGED, dataSnapshot, s, null);
if (isDeliveryPaused) {
mEvents.add(event);
} else {
mSubject.onNext(event);
}
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
ChildEvent event = new ChildEvent(FirebaseChildType.CHILD_REMOVED, dataSnapshot, null, null);
if (isDeliveryPaused) {
mEvents.add(event);
} else {
mSubject.onNext(event);
}
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
ChildEvent event = new ChildEvent(FirebaseChildType.CHILD_MOVED, dataSnapshot, s, null);
if (isDeliveryPaused) {
mEvents.add(event);
} else {
mSubject.onNext(event);
}
}
@Override
public void onCancelled(FirebaseError firebaseError) {
ChildEvent event = new ChildEvent(FirebaseChildType.CANCELLED, null, null, firebaseError);
if (isDeliveryPaused) {
mEvents.add(event);
} else {
mSubject.onNext(event);
}
}
}