Skip to content

Commit 7e66871

Browse files
author
Kaushik Gopal
committed
refactor: use Subject for searching demo
1 parent 87c4492 commit 7e66871

File tree

6 files changed

+221
-5
lines changed

6 files changed

+221
-5
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package com.morihacky.android.rxjava;
2+
3+
import android.content.Context;
4+
import android.os.Bundle;
5+
import android.os.Handler;
6+
import android.os.Looper;
7+
import android.support.annotation.Nullable;
8+
import android.support.v4.app.Fragment;
9+
import android.view.LayoutInflater;
10+
import android.view.View;
11+
import android.view.ViewGroup;
12+
import android.widget.ArrayAdapter;
13+
import android.widget.ListView;
14+
15+
import com.morihacky.android.rxjava.app.R;
16+
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
20+
import butterknife.ButterKnife;
21+
import butterknife.InjectView;
22+
import butterknife.OnTextChanged;
23+
import rx.Observable;
24+
import rx.Observer;
25+
import rx.Subscriber;
26+
import rx.Subscription;
27+
import rx.android.observables.AndroidObservable;
28+
import rx.android.schedulers.AndroidSchedulers;
29+
import rx.schedulers.Schedulers;
30+
import rx.subjects.BehaviorSubject;
31+
import timber.log.Timber;
32+
33+
34+
/**
35+
* The reason we use a Subject for tracking the search query is because it emits observables.
36+
* Because a Subject subscribes to an Observable, it will trigger that Observable to begin emitting items
37+
* (if that Observable is "cold" — that is, if it waits for a subscription before it begins to emit items).
38+
* This can have the effect of making the resulting Subject a "hot" Observable variant of the original "cold" Observable.
39+
*
40+
* This allows us to create the subject and subscription one time onActivity creation. Subsequently
41+
* we send in Observables to the Subject's subscriber onTextChanged
42+
*
43+
* Otherwise we would have to create the subscription onTextChanged every single time, which is
44+
* wasteful (not really since we anyway unsubscribe at the end).
45+
* less-elegant (as a concept for sure, but not for simplicity vs. the 3 step basic subscription creation)
46+
* doesn't allow us to debounce/throttle/sample ?
47+
*
48+
*/
49+
public class BehaviorSubjectSearchEmitterFragment
50+
extends Fragment {
51+
52+
@InjectView(R.id.list_threading_log) ListView _logsList;
53+
54+
private LogAdapter _adapter;
55+
private List<String> _logs;
56+
57+
private Subscription _subscription;
58+
private BehaviorSubject<Observable<String>> _searchTextEmitterSubject;
59+
60+
@Override
61+
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
62+
super.onActivityCreated(savedInstanceState);
63+
_setupLogger();
64+
65+
_searchTextEmitterSubject = BehaviorSubject.create(Observable.<String>empty());
66+
67+
_subscription = AndroidObservable.bindFragment(BehaviorSubjectSearchEmitterFragment.this,
68+
Observable.switchOnNext(_searchTextEmitterSubject))
69+
.subscribeOn(Schedulers.io())
70+
.observeOn(AndroidSchedulers.mainThread())
71+
.subscribe(_getSearchObserver());
72+
73+
// .debounce(400, TimeUnit.MILLISECONDS, Schedulers.io())
74+
}
75+
76+
@OnTextChanged(R.id.input_txt_subject)
77+
public void onTextEntered(CharSequence charsEntered) {
78+
Timber.d("---------- text entered %s", charsEntered);
79+
_searchTextEmitterSubject.onNext(_getASearchObservableFor(charsEntered.toString()));
80+
}
81+
82+
// -----------------------------------------------------------------------------------
83+
// Main Rx entities
84+
85+
private Observable<String> _getASearchObservableFor(final String searchText) {
86+
return Observable.create(new Observable.OnSubscribe<String>() {
87+
88+
89+
@Override
90+
public void call(Subscriber<? super String> subscriber) {
91+
subscriber.onNext(searchText);
92+
subscriber.onCompleted();
93+
}
94+
});
95+
}
96+
97+
private Observer<String> _getSearchObserver() {
98+
return new Observer<String>() {
99+
100+
101+
@Override
102+
public void onCompleted() {
103+
Timber.d("--------- onComplete");
104+
}
105+
106+
@Override
107+
public void onError(Throwable e) {
108+
Timber.e(e, "--------- Woops on error!");
109+
_log(String.format("Dang error. check your logs"));
110+
}
111+
112+
@Override
113+
public void onNext(String searchText) {
114+
Timber.d("--------- onNext");
115+
_log(String.format("You searched for %s", searchText));
116+
}
117+
};
118+
}
119+
120+
@Override
121+
public void onDestroy() {
122+
super.onDestroy();
123+
_subscription.unsubscribe();
124+
}
125+
126+
// -----------------------------------------------------------------------------------
127+
// Method that help wiring up the example (irrelevant to RxJava)
128+
129+
@Override
130+
public View onCreateView(LayoutInflater inflater,
131+
@Nullable ViewGroup container,
132+
@Nullable Bundle savedInstanceState) {
133+
View layout = inflater.inflate(R.layout.fragment_subject, container, false);
134+
ButterKnife.inject(this, layout);
135+
return layout;
136+
}
137+
138+
private void _setupLogger() {
139+
_logs = new ArrayList<String>();
140+
_adapter = new LogAdapter(getActivity(), new ArrayList<String>());
141+
_logsList.setAdapter(_adapter);
142+
}
143+
144+
private void _log(String logMsg) {
145+
146+
if (_isCurrentlyOnMainThread()) {
147+
_logs.add(0, logMsg + " (main thread) ");
148+
_adapter.clear();
149+
_adapter.addAll(_logs);
150+
151+
} else {
152+
_logs.add(0, logMsg + " (NOT main thread) ");
153+
154+
// You can only do below stuff on main thread.
155+
new Handler(Looper.getMainLooper()).post(new Runnable() {
156+
157+
158+
@Override
159+
public void run() {
160+
_adapter.clear();
161+
_adapter.addAll(_logs);
162+
}
163+
});
164+
}
165+
}
166+
167+
private boolean _isCurrentlyOnMainThread() {
168+
return Looper.myLooper() == Looper.getMainLooper();
169+
}
170+
171+
private class LogAdapter
172+
extends ArrayAdapter<String> {
173+
174+
public LogAdapter(Context context, List<String> logs) {
175+
super(context, R.layout.item_log, R.id.item_log, logs);
176+
}
177+
}
178+
}

app/src/main/java/com/morihacky/android/rxjava/MainActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ protected void onCreate(Bundle savedInstanceState) {
2020
getSupportFragmentManager().beginTransaction()
2121
.addToBackStack(this.toString())
2222
// .replace(R.id.activity_main, new MainFragment(), this.toString())
23-
.replace(R.id.activity_main, new BufferDemoFragment(), this.toString())
23+
.replace(R.id.activity_main, new BehaviorSubjectSearchEmitterFragment(), this.toString())
2424
.commit();
2525
}
2626
}

app/src/main/java/com/morihacky/android/rxjava/MainFragment.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ public void demoConcurrencyWithSchedulers() {
3535
.commit();
3636
}
3737

38-
3938
@OnClick(R.id.btn_demo_buffer)
4039
public void demoBuffer() {
4140
getActivity().getSupportFragmentManager()
@@ -47,4 +46,15 @@ public void demoBuffer() {
4746
.commit();
4847
}
4948

49+
@OnClick(R.id.btn_demo_subject)
50+
public void demoThrottling() {
51+
getActivity().getSupportFragmentManager()
52+
.beginTransaction()
53+
.addToBackStack(this.toString())
54+
.replace(R.id.activity_main,
55+
new BehaviorSubjectSearchEmitterFragment(),
56+
this.toString())
57+
.commit();
58+
}
59+
5060
}

app/src/main/res/layout/fragment_main.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
android:text="@string/btn_demo_buffer"/>
2424

2525
<Button
26-
android:id="@+id/btn_demo_throttle"
26+
android:id="@+id/btn_demo_subject"
2727
android:layout_height="wrap_content"
2828
android:layout_width="wrap_content"
29-
android:text="@string/btn_demo_throttle"/>
29+
android:text="@string/btn_demo_subject"/>
3030

3131
</LinearLayout>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<LinearLayout
4+
android:orientation="vertical"
5+
android:layout_height="match_parent"
6+
android:layout_width="match_parent"
7+
xmlns:android="http://schemas.android.com/apk/res/android">
8+
9+
<TextView
10+
android:layout_height="wrap_content"
11+
android:layout_width="match_parent"
12+
android:padding="10dp"
13+
android:gravity="center"
14+
android:text="@string/msg_demo_subject"/>
15+
16+
<EditText
17+
android:id="@+id/input_txt_subject"
18+
android:layout_height="wrap_content"
19+
android:layout_width="match_parent"
20+
android:textSize="16sp"
21+
android:hint="Enter some search text"/>
22+
23+
<ListView
24+
android:id="@+id/list_threading_log"
25+
android:layout_height="match_parent"
26+
android:layout_width="match_parent"/>
27+
</LinearLayout>

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77

88
<string name="msg_demo_buffer">This is a demo of how events can be accumulated using the "buffer" operation. Tap the button below repetitively and you will notice in the logs that button taps are collected over a span of 2s and printed below.</string>
99
<string name="msg_demo_concurrency_schedulers">This is a demo of how long running operations can be offloaded to a background thread. After the operation is done, we resume back on the main thread. All using RxJava! \n\n To really see this shine. Hit the button multiple times and see how the button click which is a ui operation is never blocked because the long operation only runs in the background</string>
10+
<string name="msg_demo_subject">As you type in the input box, it will not shoot out log messages at every single input character change, but rather only pick the lastly emitted event (i.e. input) and log that. \n\nThis is the debounce/throttleWithTimeout method in RxJava.</string>
1011

1112
<string name="btn_demo_schedulers">bg work (schedulers &amp; concurrency)</string>
1213
<string name="btn_demo_buffer">accumulate calls (buffer)</string>
13-
<string name="btn_demo_throttle">instant search (throttle)</string>
14+
<string name="btn_demo_subject">instant search (subject throttling?)</string>
1415

1516
</resources>

0 commit comments

Comments
 (0)