Skip to content

Commit cb08eba

Browse files
committed
Revised version of Android UI component operator
1 parent af2b35a commit cb08eba

File tree

2 files changed

+285
-1
lines changed

2 files changed

+285
-1
lines changed

rxjava-contrib/rxjava-android/build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ apply plugin: 'osgi'
22

33
dependencies {
44
compile project(':rxjava-core')
5+
provided 'com.google.android:android:4.0.1.2'
6+
provided 'com.google.android:support-v4:r7'
7+
8+
// testing
59
provided 'junit:junit-dep:4.10'
610
provided 'org.mockito:mockito-core:1.8.5'
711
provided 'org.robolectric:robolectric:2.1.1'
8-
provided 'com.google.android:android:4.0.1.2'
912
}
1013

1114
javadoc {
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
package rx.operators;
2+
3+
import static org.mockito.Matchers.any;
4+
import static org.mockito.Matchers.anyInt;
5+
import static org.mockito.Mockito.never;
6+
import static org.mockito.Mockito.times;
7+
import static org.mockito.Mockito.verify;
8+
import static org.mockito.Mockito.verifyNoMoreInteractions;
9+
import static org.mockito.Mockito.when;
10+
11+
import org.junit.Before;
12+
import org.junit.Test;
13+
import org.junit.runner.RunWith;
14+
import org.mockito.Mock;
15+
import org.mockito.MockitoAnnotations;
16+
import org.robolectric.RobolectricTestRunner;
17+
import org.robolectric.annotation.Config;
18+
import rx.Observable;
19+
import rx.Observer;
20+
import rx.Subscription;
21+
import rx.android.concurrency.AndroidSchedulers;
22+
import rx.subjects.PublishSubject;
23+
24+
import android.app.Activity;
25+
import android.app.Fragment;
26+
import android.util.Log;
27+
28+
import java.lang.ref.WeakReference;
29+
30+
public class OperationObserveInForeground {
31+
32+
public static <T> Observable<T> observeOnFragment(Observable<T> source, android.app.Fragment fragment) {
33+
return Observable.create(new OnSubscribeFragment<T>(source, new WeakReference<Fragment>(fragment)));
34+
}
35+
36+
public static <T> Observable<T> observeOnSupportFragment(Observable<T> source, android.support.v4.app.Fragment fragment) {
37+
return Observable.create(new OnSubscribeSupportFragment<T>(
38+
source, new WeakReference<android.support.v4.app.Fragment>(fragment)));
39+
}
40+
41+
public static <T> Observable<T> observeOnActivity(Observable<T> source, Activity activity) {
42+
return Observable.create(new OnSubscribeActivity<T>(source, new WeakReference<Activity>(activity)));
43+
}
44+
45+
private static abstract class OnSubscribeBase<T, AndroidComponent> implements Observable.OnSubscribeFunc<T> {
46+
47+
private final Observable<T> source;
48+
private final WeakReference<AndroidComponent> componentRef;
49+
private Observer<? super T> actual;
50+
51+
private OnSubscribeBase(Observable<T> source, WeakReference<AndroidComponent> componentRef) {
52+
this.source = source;
53+
this.componentRef = componentRef;
54+
}
55+
56+
protected abstract boolean isComponentValid(AndroidComponent component);
57+
58+
@Override
59+
public Subscription onSubscribe(Observer<? super T> observer) {
60+
actual = observer;
61+
return source.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<T>() {
62+
@Override
63+
public void onCompleted() {
64+
AndroidComponent component = componentRef.get();
65+
if (component != null && isComponentValid(component)) {
66+
actual.onCompleted();
67+
} else {
68+
actual = null;
69+
log("onComplete: target component released or detached; dropping message");
70+
}
71+
}
72+
73+
@Override
74+
public void onError(Throwable e) {
75+
AndroidComponent component = componentRef.get();
76+
if (component != null && isComponentValid(component)) {
77+
actual.onError(e);
78+
} else {
79+
actual = null;
80+
log("onError: target component released or detached; dropping message");
81+
}
82+
}
83+
84+
@Override
85+
public void onNext(T args) {
86+
AndroidComponent component = componentRef.get();
87+
if (component != null && isComponentValid(component)) {
88+
actual.onNext(args);
89+
} else {
90+
actual = null;
91+
log("onNext: target component released or detached; dropping message");
92+
}
93+
}
94+
95+
private void log(String message) {
96+
Log.i(OperationObserveInForeground.class.getSimpleName(), message);
97+
}
98+
});
99+
}
100+
}
101+
102+
private static final class OnSubscribeFragment<T> extends OnSubscribeBase<T, android.app.Fragment> {
103+
104+
private OnSubscribeFragment(Observable<T> source, WeakReference<android.app.Fragment> fragmentRef) {
105+
super(source, fragmentRef);
106+
}
107+
108+
@Override
109+
protected boolean isComponentValid(android.app.Fragment fragment) {
110+
return fragment.isAdded();
111+
}
112+
}
113+
114+
private static final class OnSubscribeSupportFragment<T> extends OnSubscribeBase<T, android.support.v4.app.Fragment> {
115+
116+
private OnSubscribeSupportFragment(Observable<T> source, WeakReference<android.support.v4.app.Fragment> fragment) {
117+
super(source, fragment);
118+
}
119+
120+
@Override
121+
protected boolean isComponentValid(android.support.v4.app.Fragment fragment) {
122+
return fragment.isAdded();
123+
}
124+
}
125+
126+
private static final class OnSubscribeActivity<T> extends OnSubscribeBase<T, Activity> {
127+
128+
private OnSubscribeActivity(Observable<T> source, WeakReference<Activity> activity) {
129+
super(source, activity);
130+
}
131+
132+
@Override
133+
protected boolean isComponentValid(Activity activity) {
134+
return !activity.isFinishing();
135+
}
136+
}
137+
138+
@RunWith(RobolectricTestRunner.class)
139+
@Config(manifest = Config.NONE)
140+
public static final class UnitTest {
141+
142+
@Mock
143+
private Observer<Integer> mockObserver;
144+
145+
@Mock
146+
private Fragment mockFragment;
147+
148+
@Mock
149+
private Activity mockActivity;
150+
151+
@Mock
152+
private Observable<Integer> mockObservable;
153+
154+
@Before
155+
public void setupMocks() {
156+
MockitoAnnotations.initMocks(this);
157+
when(mockFragment.isAdded()).thenReturn(true);
158+
}
159+
160+
@Test
161+
public void itObservesTheSourceSequenceOnTheMainUIThread() {
162+
OperationObserveInForeground.observeOnFragment(mockObservable, mockFragment).subscribe(mockObserver);
163+
verify(mockObservable).observeOn(AndroidSchedulers.mainThread());
164+
}
165+
166+
@Test
167+
public void itForwardsOnNextOnCompletedSequenceToTargetObserver() {
168+
Observable<Integer> source = Observable.from(1, 2, 3);
169+
OperationObserveInForeground.observeOnFragment(source, mockFragment).subscribe(mockObserver);
170+
verify(mockObserver, times(3)).onNext(anyInt());
171+
verify(mockObserver).onCompleted();
172+
verify(mockObserver, never()).onError(any(Exception.class));
173+
}
174+
175+
@Test
176+
public void itForwardsOnErrorToTargetObserver() {
177+
final Exception exception = new Exception();
178+
Observable<Integer> source = Observable.error(exception);
179+
OperationObserveInForeground.observeOnFragment(source, mockFragment).subscribe(mockObserver);
180+
verify(mockObserver).onError(exception);
181+
verify(mockObserver, never()).onNext(anyInt());
182+
verify(mockObserver, never()).onCompleted();
183+
}
184+
185+
@Test
186+
public void itDropsOnNextOnCompletedSequenceIfTargetComponentIsGone() {
187+
PublishSubject<Integer> source = PublishSubject.create();
188+
WeakReference<Fragment> fragmentRef = new WeakReference<Fragment>(mockFragment);
189+
190+
final OnSubscribeFragment<Integer> operator = new OnSubscribeFragment<Integer>(source, fragmentRef);
191+
operator.onSubscribe(mockObserver);
192+
193+
source.onNext(1);
194+
fragmentRef.clear();
195+
196+
source.onNext(2);
197+
source.onNext(3);
198+
source.onCompleted();
199+
200+
verify(mockObserver).onNext(1);
201+
verifyNoMoreInteractions(mockObserver);
202+
}
203+
204+
@Test
205+
public void itDropsOnErrorIfTargetComponentIsGone() {
206+
PublishSubject<Integer> source = PublishSubject.create();
207+
WeakReference<Fragment> fragmentRef = new WeakReference<Fragment>(mockFragment);
208+
209+
final OnSubscribeFragment<Integer> operator = new OnSubscribeFragment<Integer>(source, fragmentRef);
210+
operator.onSubscribe(mockObserver);
211+
212+
source.onNext(1);
213+
fragmentRef.clear();
214+
215+
source.onError(new Exception());
216+
217+
verify(mockObserver).onNext(1);
218+
verifyNoMoreInteractions(mockObserver);
219+
}
220+
221+
@Test
222+
public void itDoesNotForwardOnNextOnCompletedSequenceIfFragmentIsDetached() {
223+
PublishSubject<Integer> source = PublishSubject.create();
224+
OperationObserveInForeground.observeOnFragment(source, mockFragment).subscribe(mockObserver);
225+
226+
source.onNext(1);
227+
228+
when(mockFragment.isAdded()).thenReturn(false);
229+
source.onNext(2);
230+
source.onNext(3);
231+
source.onCompleted();
232+
233+
verify(mockObserver).onNext(1);
234+
verify(mockObserver, never()).onCompleted();
235+
}
236+
237+
@Test
238+
public void itDoesNotForwardOnErrorIfFragmentIsDetached() {
239+
PublishSubject<Integer> source = PublishSubject.create();
240+
OperationObserveInForeground.observeOnFragment(source, mockFragment).subscribe(mockObserver);
241+
242+
source.onNext(1);
243+
244+
when(mockFragment.isAdded()).thenReturn(false);
245+
source.onError(new Exception());
246+
247+
verify(mockObserver).onNext(1);
248+
verify(mockObserver, never()).onError(any(Exception.class));
249+
}
250+
251+
@Test
252+
public void isDoesNotForwardOnNextOnCompletedSequenceIfActivityIsFinishing() {
253+
PublishSubject<Integer> source = PublishSubject.create();
254+
OperationObserveInForeground.observeOnActivity(source, mockActivity).subscribe(mockObserver);
255+
256+
source.onNext(1);
257+
258+
when(mockActivity.isFinishing()).thenReturn(true);
259+
source.onNext(2);
260+
source.onNext(3);
261+
source.onCompleted();
262+
263+
verify(mockObserver).onNext(1);
264+
verify(mockObserver, never()).onCompleted();
265+
}
266+
267+
@Test
268+
public void itDoesNotForwardOnErrorIfActivityIsFinishing() {
269+
PublishSubject<Integer> source = PublishSubject.create();
270+
OperationObserveInForeground.observeOnActivity(source, mockActivity).subscribe(mockObserver);
271+
272+
source.onNext(1);
273+
274+
when(mockActivity.isFinishing()).thenReturn(true);
275+
source.onError(new Exception());
276+
277+
verify(mockObserver).onNext(1);
278+
verify(mockObserver, never()).onError(any(Exception.class));
279+
}
280+
}
281+
}

0 commit comments

Comments
 (0)