From b99b513e1368a87fac8c287da7076cedad24f04e Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Fri, 5 Jun 2015 11:21:16 -0700 Subject: [PATCH 001/178] 1. use ViewObservable for the clicks vs attaching a listener on the button 2. remove _tapCount as it's unnecessary since buffer does the accumulation for us already 3. add comment to indicate there's a much better way to do this as demonstrated in Event Bus 3 demo. Thanks to @vanniktech for kickstarting these changes https://github.com/kaushikgopal/Android-RxJava/pull/14/files --- .../android/rxjava/BufferDemoFragment.java | 128 ++++++++---------- 1 file changed, 54 insertions(+), 74 deletions(-) diff --git a/app/src/main/java/com/morihacky/android/rxjava/BufferDemoFragment.java b/app/src/main/java/com/morihacky/android/rxjava/BufferDemoFragment.java index 16b177e7..e0ee6152 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/BufferDemoFragment.java +++ b/app/src/main/java/com/morihacky/android/rxjava/BufferDemoFragment.java @@ -1,6 +1,5 @@ package com.morihacky.android.rxjava; -import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -8,29 +7,35 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import butterknife.ButterKnife; import butterknife.InjectView; -import com.morihacky.android.rxjava.R; +import com.morihacky.android.rxjava.wiring.LogAdapter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -import rx.Observable; import rx.Observer; -import rx.Subscriber; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; +import rx.android.view.OnClickEvent; +import rx.android.view.ViewObservable; +import rx.functions.Func1; import timber.log.Timber; /** - * credit to @tomrozb for this implementation: - * http://stackoverflow.com/questions/24922610/incorrect-understanding-of-buffer-in-rxjava + * This is a demonstration of the `buffer` Observable. * - * An alternate mechanism of achieving the same result would be to use a {@link rx.subjects.PublishSubject} - * as demonstrated in the case of {@link com.morihacky.android.rxjava.SubjectDebounceSearchEmitterFragment} + * The buffer observable allows taps to be collected only within a time span. So taps outside the + * 2s limit imposed by buffer will get accumulated in the next log statement. + * + * If you're looking for a more foolproof solution that accumulates "continuous" taps vs + * a more dumb solution as show below (i.e. number of taps within a timespan) + * look at {@link com.morihacky.android.rxjava.rxbus.RxBusDemo_Bottom3Fragment} where a combo + * of `publish` and `buffer` is used. + * + * Also http://nerds.weddingpartyapp.com/tech/2015/01/05/debouncedbuffer-used-in-rxbus-example/ + * if you're looking for words instead of code */ public class BufferDemoFragment extends BaseFragment { @@ -40,14 +45,13 @@ public class BufferDemoFragment private LogAdapter _adapter; private List _logs; - private int _tapCount = 0; private Subscription _subscription; @Override - public void onResume() { - super.onResume(); - _subscription = _getBufferedObservable().subscribe(_getObserver()); + public void onStart() { + super.onStart(); + _subscription = _getBufferedSubscription(); } @Override @@ -74,65 +78,49 @@ public View onCreateView(LayoutInflater inflater, // ----------------------------------------------------------------------------------- // Main Rx entities - private Observable> _getBufferedObservable() { - - return Observable.create(new Observable.OnSubscribe() { - - @Override - public void call(Subscriber subscriber) { - _tapBtn.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - Timber.d("--------- GOT A TAP"); - _tapCount += 1; - _log("GOT A TAP"); - } - }); - } - }) + private Subscription _getBufferedSubscription() { + return ViewObservable.clicks(_tapBtn) + .map(new Func1() { + @Override + public Integer call(OnClickEvent onClickEvent) { + Timber.d("--------- GOT A TAP"); + _log("GOT A TAP"); + return 1; + } + }) .buffer(2, TimeUnit.SECONDS) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - } - - private Observer> _getObserver() { - return new Observer>() { - - @Override - public void onCompleted() { - if (_tapCount > 0) { - _log(String.format("%d taps", _tapCount)); - _tapCount = 0; - } - } - - @Override - public void onError(Throwable e) { - Timber.e(e, "--------- Woops on error!"); - _log(String.format("Dang error. check your logs")); - } - - @Override - public void onNext(List integers) { - Timber.d("--------- onNext"); - if (integers.size() > 0) { - for (int i : integers) { - _tapCount += i; - } - } else { - Timber.d("--------- No taps received "); - } - onCompleted(); - } - }; + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer>() { + + @Override + public void onCompleted() { + // fyi: you'll never reach here + Timber.d("----- onCompleted"); + } + + @Override + public void onError(Throwable e) { + Timber.e(e, "--------- Woops on error!"); + _log("Dang error! check your logs"); + } + + @Override + public void onNext(List integers) { + Timber.d("--------- onNext"); + if (integers.size() > 0) { + _log(String.format("%d taps", integers.size())); + } else { + Timber.d("--------- No taps received "); + } + } + }); } // ----------------------------------------------------------------------------------- // Methods that help wiring up the example (irrelevant to RxJava) private void _setupLogger() { - _logs = new ArrayList(); + _logs = new ArrayList<>(); _adapter = new LogAdapter(getActivity(), new ArrayList()); _logsList.setAdapter(_adapter); } @@ -161,12 +149,4 @@ public void run() { private boolean _isCurrentlyOnMainThread() { return Looper.myLooper() == Looper.getMainLooper(); } - - private class LogAdapter - extends ArrayAdapter { - - public LogAdapter(Context context, List logs) { - super(context, R.layout.item_log, R.id.item_log, logs); - } - } } From e80566706ef373ee997a50c4e67beaa93930c93a Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Fri, 5 Jun 2015 11:29:14 -0700 Subject: [PATCH 002/178] fix: correct readme doc for buffered demo --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3091a8bf..d403de60 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,9 @@ A button is provided and we accumulate the number of clicks on that button, over If you hit the button once. you'll get message saying the button was hit once. If you hit it 5 times continuosly within a span of 2 seconds, then you get a single log, saying you hit that button 5 times (vs 5 individual logs saying "Button hit once"). -Two possible implementations: +Note: -2a. Using a traditional observable - but encompassing the OnClick within the observable (as demoed here) -2b. Using PublishSubject and sending single clicks to the Observable, which in-turn then sends it to the Observer +If you're looking for a more foolproof solution that accumulates "continuous" taps vs just the number of taps within a time span, look at the [EventBus Demo](https://github.com/kaushikgopal/Android-RxJava/blob/master/app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom3Fragment.java) where a combo of the `publish` and `buffer` operators is used. For a more detailed explanation you can also have a look at this [blog post](http://nerds.weddingpartyapp.com/tech/2015/01/05/debouncedbuffer-used-in-rxbus-example/). ### Instant/Auto searching (subject + debounce) From eb1274843453a6c7b037fd1f562883a1f30bad6a Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Sun, 7 Jun 2015 12:24:03 -0700 Subject: [PATCH 003/178] fix: comment no longer holds --- .../SubjectDebounceSearchEmitterFragment.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/app/src/main/java/com/morihacky/android/rxjava/SubjectDebounceSearchEmitterFragment.java b/app/src/main/java/com/morihacky/android/rxjava/SubjectDebounceSearchEmitterFragment.java index eef363de..2dab00a8 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/SubjectDebounceSearchEmitterFragment.java +++ b/app/src/main/java/com/morihacky/android/rxjava/SubjectDebounceSearchEmitterFragment.java @@ -29,23 +29,6 @@ import static java.lang.String.format; import static rx.android.app.AppObservable.bindFragment; -/** - * The reason we use a Subject for tracking the search query is because it emits observables. - * Because a Subject subscribes to an Observable, it will trigger that Observable to begin emitting items - * (if that Observable is "cold" — that is, if it waits for a subscription before it begins to emit items). - * This can have the effect of making the resulting Subject a "hot" Observable variant of the original "cold" Observable. - * - * This allows us to create the subject and subscription one time onActivity creation - * Subsequently we send in Observables to the Subject's subscriber onTextChanged - * - * (unlike the way it's done in {@link com.morihacky.android.rxjava.ConcurrencyWithSchedulersDemoFragment#startLongOperation()}) - * where we create the subscription on every single event change (OnClick or OnTextchanged) which is - * - * wasteful! : not really since we anyway unsubscribe in OnDestroyView - * less-elegant : as a concept for sure - * simpler actually : adds one more step in the 3 step subscription process, where we create emitter, and then send observables to that emitter) - * incapable of debounce : this is the primary reason, since creating new observable everytime in subscription disregards debounce on subsequent calls - */ public class SubjectDebounceSearchEmitterFragment extends BaseFragment { From d6566c969d864950d6c1b86879c321abccf54604 Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Sun, 7 Jun 2015 12:25:35 -0700 Subject: [PATCH 004/178] refactor: rename SubjectDebounceSearchEmitter to just DebounceEmitter cause we're no longer using a Subject --- ...hEmitterFragment.java => DebounceSearchEmitterFragment.java} | 2 +- .../main/java/com/morihacky/android/rxjava/MainFragment.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename app/src/main/java/com/morihacky/android/rxjava/{SubjectDebounceSearchEmitterFragment.java => DebounceSearchEmitterFragment.java} (98%) diff --git a/app/src/main/java/com/morihacky/android/rxjava/SubjectDebounceSearchEmitterFragment.java b/app/src/main/java/com/morihacky/android/rxjava/DebounceSearchEmitterFragment.java similarity index 98% rename from app/src/main/java/com/morihacky/android/rxjava/SubjectDebounceSearchEmitterFragment.java rename to app/src/main/java/com/morihacky/android/rxjava/DebounceSearchEmitterFragment.java index 2dab00a8..3db01712 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/SubjectDebounceSearchEmitterFragment.java +++ b/app/src/main/java/com/morihacky/android/rxjava/DebounceSearchEmitterFragment.java @@ -29,7 +29,7 @@ import static java.lang.String.format; import static rx.android.app.AppObservable.bindFragment; -public class SubjectDebounceSearchEmitterFragment +public class DebounceSearchEmitterFragment extends BaseFragment { @InjectView(R.id.list_threading_log) ListView _logsList; diff --git a/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java b/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java index e5d40374..b8a37ac2 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java +++ b/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java @@ -47,7 +47,7 @@ public void demoThrottling() { .beginTransaction() .addToBackStack(this.toString()) .replace(R.id.activity_main, - new SubjectDebounceSearchEmitterFragment(), + new DebounceSearchEmitterFragment(), this.toString()) .commit(); } From befeb858506e3448567ad3040bfacb2cddde0a3f Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Sun, 7 Jun 2015 12:28:20 -0700 Subject: [PATCH 005/178] bef --- .../android/rxjava/DebounceSearchEmitterFragment.java | 7 +++---- .../java/com/morihacky/android/rxjava/MainFragment.java | 2 +- ...fragment_subject_debounce.xml => fragment_debounce.xml} | 6 +++--- app/src/main/res/layout/fragment_main.xml | 4 ++-- app/src/main/res/values/strings.xml | 4 ++-- 5 files changed, 11 insertions(+), 12 deletions(-) rename app/src/main/res/layout/{fragment_subject_debounce.xml => fragment_debounce.xml} (89%) diff --git a/app/src/main/java/com/morihacky/android/rxjava/DebounceSearchEmitterFragment.java b/app/src/main/java/com/morihacky/android/rxjava/DebounceSearchEmitterFragment.java index 3db01712..6a71d07c 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/DebounceSearchEmitterFragment.java +++ b/app/src/main/java/com/morihacky/android/rxjava/DebounceSearchEmitterFragment.java @@ -23,7 +23,6 @@ import rx.android.schedulers.AndroidSchedulers; import rx.android.widget.OnTextChangeEvent; import rx.android.widget.WidgetObservable; -import rx.schedulers.Schedulers; import timber.log.Timber; import static java.lang.String.format; @@ -33,7 +32,7 @@ public class DebounceSearchEmitterFragment extends BaseFragment { @InjectView(R.id.list_threading_log) ListView _logsList; - @InjectView(R.id.input_txt_subject_debounce) EditText _inputSearchText; + @InjectView(R.id.input_txt_debounce) EditText _inputSearchText; private LogAdapter _adapter; private List _logs; @@ -52,12 +51,12 @@ public void onDestroy() { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.fragment_subject_debounce, container, false); + View layout = inflater.inflate(R.layout.fragment_debounce, container, false); ButterKnife.inject(this, layout); return layout; } - @OnClick(R.id.clr_subject_debounce) + @OnClick(R.id.clr_debounce) public void onClearLog() { _logs = new ArrayList<>(); _adapter.clear(); diff --git a/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java b/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java index b8a37ac2..ce9b926f 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java +++ b/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java @@ -41,7 +41,7 @@ public void demoBuffer() { .commit(); } - @OnClick(R.id.btn_demo_subject_debounce) + @OnClick(R.id.btn_demo_debounce) public void demoThrottling() { getActivity().getSupportFragmentManager() .beginTransaction() diff --git a/app/src/main/res/layout/fragment_subject_debounce.xml b/app/src/main/res/layout/fragment_debounce.xml similarity index 89% rename from app/src/main/res/layout/fragment_subject_debounce.xml rename to app/src/main/res/layout/fragment_debounce.xml index 85a6f8c1..2dc599c6 100644 --- a/app/src/main/res/layout/fragment_subject_debounce.xml +++ b/app/src/main/res/layout/fragment_debounce.xml @@ -12,7 +12,7 @@ android:layout_width="match_parent" android:padding="10dp" android:gravity="center" - android:text="@string/msg_demo_subject" + android:text="@string/msg_demo_debounce" />