Prevent observers from sending events out of order, add documentation, annotations

This commit is contained in:
Anthony Restaino 2016-02-02 22:06:28 -05:00
parent 84627b3fae
commit ac3f43a76f
9 changed files with 119 additions and 16 deletions

View File

@ -20,7 +20,7 @@ public class IncognitoActivity extends BrowserActivity {
public Observable<Void> updateCookiePreference() { public Observable<Void> updateCookiePreference() {
return Observable.create(new Action<Void>() { return Observable.create(new Action<Void>() {
@Override @Override
public void onSubscribe(Subscriber<Void> subscriber) { public void onSubscribe(@NonNull Subscriber<Void> subscriber) {
CookieManager cookieManager = CookieManager.getInstance(); CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieSyncManager.createInstance(IncognitoActivity.this); CookieSyncManager.createInstance(IncognitoActivity.this);

View File

@ -20,7 +20,7 @@ public class MainActivity extends BrowserActivity {
public Observable<Void> updateCookiePreference() { public Observable<Void> updateCookiePreference() {
return Observable.create(new Action<Void>() { return Observable.create(new Action<Void>() {
@Override @Override
public void onSubscribe(Subscriber<Void> subscriber) { public void onSubscribe(@NonNull Subscriber<Void> subscriber) {
CookieManager cookieManager = CookieManager.getInstance(); CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieSyncManager.createInstance(MainActivity.this); CookieSyncManager.createInstance(MainActivity.this);

View File

@ -17,7 +17,6 @@ import java.lang.ref.WeakReference;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;

View File

@ -3,5 +3,14 @@ package acr.browser.lightning.react;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
public interface Action<T> { public interface Action<T> {
/**
* Should be overridden to send the subscriber
* events such as {@link Subscriber#onNext(Object)}
* or {@link Subscriber#onComplete()}.
*
* @param subscriber the subscriber that is sent in
* when the user of the Observable
* subscribes.
*/
void onSubscribe(@NonNull Subscriber<T> subscriber); void onSubscribe(@NonNull Subscriber<T> subscriber);
} }

View File

@ -3,6 +3,7 @@ package acr.browser.lightning.react;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -18,6 +19,8 @@ import acr.browser.lightning.utils.Preconditions;
*/ */
public class Observable<T> { public class Observable<T> {
private static final String TAG = Observable.class.getSimpleName();
@NonNull private Action<T> mAction; @NonNull private Action<T> mAction;
@Nullable private Executor mSubscriber; @Nullable private Executor mSubscriber;
@Nullable private Executor mObserver; @Nullable private Executor mObserver;
@ -30,54 +33,99 @@ public class Observable<T> {
mDefault = new ThreadExecutor(looper); mDefault = new ThreadExecutor(looper);
} }
/**
* Static creator method that creates an Observable from the
* {@link Action} that is passed in as the parameter. Action
* must not be null.
*
* @param action the Action to perform
* @param <T> the type that will be emitted to the subscriber
* @return a valid non-null Observable.
*/
@NonNull
public static <T> Observable<T> create(@NonNull Action<T> action) { public static <T> Observable<T> create(@NonNull Action<T> action) {
Preconditions.checkNonNull(action); Preconditions.checkNonNull(action);
return new Observable<>(action); return new Observable<>(action);
} }
/**
* Tells the Observable what Executor that the subscription
* work should run on.
*
* @param subscribeExecutor the Executor to run the work on.
* @return returns this so that calls can be conveniently chained.
*/
public Observable<T> subscribeOn(@NonNull Executor subscribeExecutor) { public Observable<T> subscribeOn(@NonNull Executor subscribeExecutor) {
mSubscriber = subscribeExecutor; mSubscriber = subscribeExecutor;
return this; return this;
} }
/**
* Tells the Observable what Executor the subscriber should observe
* the work on.
*
* @param observerExecutor the Executor to run to callback on.
* @return returns this so that calls can be conveniently chained.
*/
public Observable<T> observeOn(@NonNull Executor observerExecutor) { public Observable<T> observeOn(@NonNull Executor observerExecutor) {
mObserver = observerExecutor; mObserver = observerExecutor;
return this; return this;
} }
/**
* Subscribes immediately to the Observable and ignores
* all onComplete and onNext calls.
*/
public void subscribe() { public void subscribe() {
executeOnSubscriberThread(new Runnable() { executeOnSubscriberThread(new Runnable() {
@Override @Override
public void run() { public void run() {
mAction.onSubscribe(new Subscriber<T>() { mAction.onSubscribe(new Subscriber<T>() {
@Override @Override
public void onComplete() { public void onComplete() {}
}
@Override @Override
public void onNext(T item) { public void onNext(T item) {}
}
}); });
} }
}); });
} }
/**
* Immediately subscribes to the Observable and starts
* sending events from the Observable to the {@link Subscription}.
*
* @param subscription the class that wishes to receive onNext and
* onComplete callbacks from the Observable.
*/
public void subscribe(@NonNull final Subscription<T> subscription) { public void subscribe(@NonNull final Subscription<T> subscription) {
Preconditions.checkNonNull(subscription); Preconditions.checkNonNull(subscription);
executeOnSubscriberThread(new Runnable() { executeOnSubscriberThread(new Runnable() {
private boolean mOnCompleteExecuted = false;
@Override @Override
public void run() { public void run() {
mAction.onSubscribe(new Subscriber<T>() { mAction.onSubscribe(new Subscriber<T>() {
@Override @Override
public void onComplete() { public void onComplete() {
if (!mOnCompleteExecuted) {
mOnCompleteExecuted = true;
executeOnObserverThread(new OnCompleteRunnable(subscription)); executeOnObserverThread(new OnCompleteRunnable(subscription));
} else {
Log.e(TAG, "onComplete called more than once");
throw new RuntimeException("onComplete called more than once");
}
} }
@Override @Override
public void onNext(final T item) { public void onNext(final T item) {
if (!mOnCompleteExecuted) {
executeOnObserverThread(new OnNextRunnable(subscription, item)); executeOnObserverThread(new OnNextRunnable(subscription, item));
} else {
Log.e(TAG, "onComplete has been already called, onNext should not be called");
throw new RuntimeException("onNext should not be called after onComplete has been called");
}
} }
}); });

View File

@ -10,11 +10,21 @@ public class Schedulers {
private static final Executor sWorker = Executors.newCachedThreadPool(); private static final Executor sWorker = Executors.newCachedThreadPool();
private static final Executor sMain = new ThreadExecutor(Looper.getMainLooper()); private static final Executor sMain = new ThreadExecutor(Looper.getMainLooper());
/**
* The worker thread.
*
* @return a non-null executor.
*/
@NonNull @NonNull
public static Executor worker() { public static Executor worker() {
return sWorker; return sWorker;
} }
/**
* The main thread.
*
* @return a non-null executor that does work on the main thread.
*/
@NonNull @NonNull
public static Executor main() { public static Executor main() {
return sMain; return sMain;

View File

@ -1,7 +1,25 @@
package acr.browser.lightning.react; package acr.browser.lightning.react;
public interface Subscriber<T> { import android.support.annotation.Nullable;
void onNext(T item);
public interface Subscriber<T> {
/**
* Called when the Observer emits an
* item. It can be called multiple times.
* It cannot be called after onComplete
* has been called.
*
* @param item the item that has been emitted,
* can be null.
*/
void onNext(@Nullable T item);
/**
* This method is called when the observer is
* finished sending the subscriber events. It
* is guaranteed that no other methods will be
* called on the Subscription after this method
* has been called.
*/
void onComplete(); void onComplete();
} }

View File

@ -1,7 +1,26 @@
package acr.browser.lightning.react; package acr.browser.lightning.react;
public interface Subscription<T> { import android.support.annotation.Nullable;
void onNext(T item);
public interface Subscription<T> {
/**
* Called when the Observer emits an
* item. It can be called multiple times.
* It cannot be called after onComplete
* has been called.
*
* @param item the item that has been emitted,
* can be null.
*/
void onNext(@Nullable T item);
/**
* This method is called when the observer is
* finished sending the subscriber events. It
* is guaranteed that no other methods will be
* called on the Subscription after this method
* has been called.
*/
void onComplete(); void onComplete();
} }

View File

@ -417,7 +417,7 @@ public class LightningView {
private Observable<File> getPathObservable(final String subFolder) { private Observable<File> getPathObservable(final String subFolder) {
return Observable.create(new Action<File>() { return Observable.create(new Action<File>() {
@Override @Override
public void onSubscribe(Subscriber<File> subscriber) { public void onSubscribe(@NonNull Subscriber<File> subscriber) {
File file = BrowserApp.get(mActivity).getDir(subFolder, 0); File file = BrowserApp.get(mActivity).getDir(subFolder, 0);
subscriber.onNext(file); subscriber.onNext(file);
subscriber.onComplete(); subscriber.onComplete();