Skip to content

Commit b232482

Browse files
committed
Merge pull request ReactiveX#70 from dpsm/0.x
Refactoring OperatorViewDetachedFromWindowFirst
2 parents 48653fc + 30453f4 commit b232482

File tree

3 files changed

+97
-17
lines changed

3 files changed

+97
-17
lines changed

rxandroid/src/main/java/rx/android/view/OperatorViewDetachedFromWindowFirst.java renamed to rxandroid/src/main/java/rx/android/view/OnSubscribeViewDetachedFromWindowFirst.java

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,32 @@
2020
import rx.Subscription;
2121

2222
/**
23-
* An internal class that is used from #{@link ViewObservable#bindView}.
2423
* This emits an event when the given #{@code View} is detached from the window for the first time.
2524
*/
26-
final class OperatorViewDetachedFromWindowFirst implements Observable.OnSubscribe<View> {
25+
final class OnSubscribeViewDetachedFromWindowFirst implements Observable.OnSubscribe<View> {
2726
private final View view;
2827

29-
public OperatorViewDetachedFromWindowFirst(View view) {
28+
public OnSubscribeViewDetachedFromWindowFirst(View view) {
3029
this.view = view;
3130
}
3231

3332
@Override
3433
public void call(final Subscriber<? super View> subscriber) {
35-
new ListenerSubscription(subscriber, view);
34+
final SubscriptionAdapter adapter = new SubscriptionAdapter(subscriber, view);
35+
subscriber.add(adapter);
36+
view.addOnAttachStateChangeListener(adapter);
3637
}
3738

3839
// This could be split into a couple of anonymous classes.
3940
// We pack it into one for the sake of memory efficiency.
40-
private static class ListenerSubscription implements View.OnAttachStateChangeListener, Subscription {
41+
private static class SubscriptionAdapter implements View.OnAttachStateChangeListener,
42+
Subscription {
4143
private Subscriber<? super View> subscriber;
4244
private View view;
4345

44-
public ListenerSubscription(Subscriber<? super View> subscriber, View view) {
46+
public SubscriptionAdapter(Subscriber<? super View> subscriber, View view) {
4547
this.subscriber = subscriber;
4648
this.view = view;
47-
view.addOnAttachStateChangeListener(this);
48-
subscriber.add(this);
4949
}
5050

5151
@Override
@@ -56,7 +56,7 @@ public void onViewAttachedToWindow(View v) {
5656
public void onViewDetachedFromWindow(View v) {
5757
if (!isUnsubscribed()) {
5858
Subscriber<? super View> originalSubscriber = subscriber;
59-
clear();
59+
unsubscribe();
6060
originalSubscriber.onNext(v);
6161
originalSubscriber.onCompleted();
6262
}
@@ -65,19 +65,15 @@ public void onViewDetachedFromWindow(View v) {
6565
@Override
6666
public void unsubscribe() {
6767
if (!isUnsubscribed()) {
68-
clear();
68+
view.removeOnAttachStateChangeListener(this);
69+
view = null;
70+
subscriber = null;
6971
}
7072
}
7173

7274
@Override
7375
public boolean isUnsubscribed() {
7476
return view == null;
7577
}
76-
77-
private void clear() {
78-
view.removeOnAttachStateChangeListener(this);
79-
view = null;
80-
subscriber = null;
81-
}
8278
}
8379
}

rxandroid/src/main/java/rx/android/view/ViewObservable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ public static <T> Observable<T> bindView(View view, Observable<T> source) {
5151
if (view == null || source == null)
5252
throw new IllegalArgumentException("View and Observable must be given");
5353
Assertions.assertUiThread();
54-
return source.takeUntil(Observable.create(new OperatorViewDetachedFromWindowFirst(view))).observeOn(mainThread());
54+
return source.takeUntil(Observable.create(new OnSubscribeViewDetachedFromWindowFirst(view))).observeOn(mainThread());
5555
}
5656
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package rx.android.view;
15+
16+
import android.view.View;
17+
18+
import org.junit.Assert;
19+
import org.junit.Test;
20+
import org.junit.runner.RunWith;
21+
import org.mockito.ArgumentCaptor;
22+
import org.mockito.Matchers;
23+
import org.robolectric.RobolectricTestRunner;
24+
25+
import rx.Observable;
26+
import rx.Subscriber;
27+
import rx.observers.TestSubscriber;
28+
29+
import static org.mockito.Mockito.mock;
30+
import static org.mockito.Mockito.never;
31+
import static org.mockito.Mockito.spy;
32+
import static org.mockito.Mockito.verify;
33+
34+
@RunWith(RobolectricTestRunner.class)
35+
public class OnSubscribeViewDetachedFromWindowFirstTest {
36+
37+
@Test
38+
public void testGivenSubscriptionWhenViewDetachedThenUnsubscribesAndRemovesListener() {
39+
final Subscriber<View> subscriber = spy(new TestSubscriber<View>());
40+
final View view = mock(View.class);
41+
final Observable<View> observable = Observable.create(new OnSubscribeViewDetachedFromWindowFirst(view));
42+
observable.subscribe(subscriber);
43+
44+
verify(subscriber, never()).onNext(view);
45+
verify(subscriber, never()).onError(Matchers.any(Throwable.class));
46+
verify(subscriber, never()).onCompleted();
47+
48+
final ArgumentCaptor<View.OnAttachStateChangeListener> captor =
49+
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
50+
verify(view).addOnAttachStateChangeListener(captor.capture());
51+
52+
final View.OnAttachStateChangeListener listener = captor.getValue();
53+
Assert.assertNotNull("Should have added listener on subscription.", listener);
54+
55+
listener.onViewDetachedFromWindow(view);
56+
57+
verify(subscriber, never()).onError(Matchers.any(Throwable.class));
58+
verify(subscriber).onNext(view);
59+
verify(subscriber).onCompleted();
60+
61+
verify(view).removeOnAttachStateChangeListener(listener);
62+
}
63+
64+
@Test
65+
public void testGivenSubscriptionWhenUnsubscribedThenStateListenerRemoved() {
66+
final Subscriber<View> subscriber = spy(new TestSubscriber<View>());
67+
final View view = mock(View.class);
68+
final Observable<View> observable = Observable.create(new OnSubscribeViewDetachedFromWindowFirst(view));
69+
observable.subscribe(subscriber).unsubscribe();
70+
71+
verify(subscriber, never()).onNext(view);
72+
verify(subscriber, never()).onError(Matchers.any(Throwable.class));
73+
verify(subscriber, never()).onCompleted();
74+
75+
final ArgumentCaptor<View.OnAttachStateChangeListener> captor =
76+
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
77+
verify(view).addOnAttachStateChangeListener(captor.capture());
78+
79+
final View.OnAttachStateChangeListener listener = captor.getValue();
80+
Assert.assertNotNull("Should have added listener on subscription.", listener);
81+
82+
verify(view).removeOnAttachStateChangeListener(listener);
83+
}
84+
}

0 commit comments

Comments
 (0)