diff --git a/rxjava-contrib/rxjava-android/build.gradle b/rxjava-contrib/rxjava-android/build.gradle index 144d3cd68a..7f50fa5858 100644 --- a/rxjava-contrib/rxjava-android/build.gradle +++ b/rxjava-contrib/rxjava-android/build.gradle @@ -8,7 +8,7 @@ dependencies { // testing provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' - provided 'org.robolectric:robolectric:2.1.1' + provided 'org.robolectric:robolectric:2.2' } javadoc { diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/ViewObservable.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/ViewObservable.java new file mode 100644 index 0000000000..bb0832b590 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/ViewObservable.java @@ -0,0 +1,41 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.android.observables; + +import android.view.View; +import android.widget.CompoundButton; +import android.widget.EditText; +import rx.Observable; +import rx.operators.OperatorCompoundButtonInput; +import rx.operators.OperatorEditTextInput; +import rx.operators.OperatorViewClick; + +public class ViewObservable { + + public static Observable clicks(final View view, final boolean emitInitialValue) { + return Observable.create(new OperatorViewClick(view, emitInitialValue)); + } + + public static Observable input(final EditText input, final boolean emitInitialValue) { + return Observable.create(new OperatorEditTextInput(input, emitInitialValue)); + } + + public static Observable input(final CompoundButton button, final boolean emitInitialValue) { + return Observable.create(new OperatorCompoundButtonInput(button, emitInitialValue)); + } + +} + diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorCompoundButtonInput.java b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorCompoundButtonInput.java new file mode 100644 index 0000000000..ca9cb7e04e --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorCompoundButtonInput.java @@ -0,0 +1,103 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import android.view.View; +import android.widget.CompoundButton; +import rx.Observable; +import rx.Observer; +import rx.Subscription; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +public class OperatorCompoundButtonInput implements Observable.OnSubscribe { + private final boolean emitInitialValue; + private final CompoundButton button; + + public OperatorCompoundButtonInput(final CompoundButton button, final boolean emitInitialValue) { + this.emitInitialValue = emitInitialValue; + this.button = button; + } + + @Override + public void call(final Observer observer) { + final CompositeOnCheckedChangeListener composite = CachedListeners.getFromViewOrCreate(button); + + final CompoundButton.OnCheckedChangeListener listener = new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(final CompoundButton button, final boolean checked) { + observer.onNext(checked); + } + }; + + final Subscription subscription = new Subscription() { + @Override + public void unsubscribe() { + composite.removeOnCheckedChangeListener(listener); + } + }; + + if (emitInitialValue) { + observer.onNext(button.isChecked()); + } + + composite.addOnCheckedChangeListener(listener); + observer.add(subscription); + } + + private static class CompositeOnCheckedChangeListener implements CompoundButton.OnCheckedChangeListener { + private final List listeners = new ArrayList(); + + public boolean addOnCheckedChangeListener(final CompoundButton.OnCheckedChangeListener listener) { + return listeners.add(listener); + } + + public boolean removeOnCheckedChangeListener(final CompoundButton.OnCheckedChangeListener listener) { + return listeners.remove(listener); + } + + @Override + public void onCheckedChanged(final CompoundButton button, final boolean checked) { + for (final CompoundButton.OnCheckedChangeListener listener : listeners) { + listener.onCheckedChanged(button, checked); + } + } + } + + private static class CachedListeners { + private static final Map sCachedListeners = new WeakHashMap(); + + public static CompositeOnCheckedChangeListener getFromViewOrCreate(final CompoundButton button) { + final CompositeOnCheckedChangeListener cached = sCachedListeners.get(button); + + if (cached != null) { + return cached; + } + + final CompositeOnCheckedChangeListener listener = new CompositeOnCheckedChangeListener(); + + sCachedListeners.put(button, listener); + button.setOnCheckedChangeListener(listener); + + return listener; + } + } +} + + diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorEditTextInput.java b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorEditTextInput.java new file mode 100644 index 0000000000..30bb377ef8 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorEditTextInput.java @@ -0,0 +1,75 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import android.text.Editable; +import android.text.TextWatcher; +import android.widget.EditText; +import rx.Observable; +import rx.Observer; +import rx.Subscription; + +public class OperatorEditTextInput implements Observable.OnSubscribe { + private final EditText input; + private final boolean emitInitialValue; + + public OperatorEditTextInput(final EditText input, final boolean emitInitialValue) { + this.input = input; + this.emitInitialValue = emitInitialValue; + } + + @Override + public void call(final Observer observer) { + final TextWatcher watcher = new SimpleTextWatcher() { + @Override + public void afterTextChanged(final Editable editable) { + observer.onNext(editable.toString()); + } + }; + + final Subscription subscription = new Subscription() { + @Override + public void unsubscribe() { + input.removeTextChangedListener(watcher); + } + }; + + if (emitInitialValue) { + observer.onNext(input.getEditableText().toString()); + } + + input.addTextChangedListener(watcher); + observer.add(subscription); + } + + private static class SimpleTextWatcher implements TextWatcher { + @Override + public void beforeTextChanged(final CharSequence sequence, final int start, final int count, final int after) { + // nothing to do + } + + @Override + public void onTextChanged(final CharSequence sequence, final int start, final int before, final int count) { + // nothing to do + } + + @Override + public void afterTextChanged(final Editable editable) { + // nothing to do + } + } +} + diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorViewClick.java b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorViewClick.java new file mode 100644 index 0000000000..a90d6a7b23 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorViewClick.java @@ -0,0 +1,99 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import android.view.View; +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +public final class OperatorViewClick implements Observable.OnSubscribe { + private final boolean emitInitialValue; + private final View view; + + public OperatorViewClick(final View view, final boolean emitInitialValue) { + this.emitInitialValue = emitInitialValue; + this.view = view; + } + + @Override + public void call(final Observer observer) { + final CompositeOnClickListener composite = CachedListeners.getFromViewOrCreate(view); + + final View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(final View clicked) { + observer.onNext(view); + } + }; + + final Subscription subscription = new Subscription() { + @Override + public void unsubscribe() { + composite.removeOnClickListener(listener); + } + }; + + if (emitInitialValue) { + observer.onNext(view); + } + + composite.addOnClickListener(listener); + observer.add(subscription); + } + + private static class CompositeOnClickListener implements View.OnClickListener { + private final List listeners = new ArrayList(); + + public boolean addOnClickListener(final View.OnClickListener listener) { + return listeners.add(listener); + } + + public boolean removeOnClickListener(final View.OnClickListener listener) { + return listeners.remove(listener); + } + + @Override + public void onClick(final View view) { + for (final View.OnClickListener listener : listeners) { + listener.onClick(view); + } + } + } + + private static class CachedListeners { + private static final Map sCachedListeners = new WeakHashMap(); + + public static CompositeOnClickListener getFromViewOrCreate(final View view) { + final CompositeOnClickListener cached = sCachedListeners.get(view); + + if (cached != null) { + return cached; + } + + final CompositeOnClickListener listener = new CompositeOnClickListener(); + + sCachedListeners.put(view, listener); + view.setOnClickListener(listener); + + return listener; + } + } +} diff --git a/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorCompoundButtonInputTest.java b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorCompoundButtonInputTest.java new file mode 100644 index 0000000000..5939f5f7c4 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorCompoundButtonInputTest.java @@ -0,0 +1,148 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.android.operators; + +import android.app.Activity; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.android.observables.ViewObservable; +import rx.observers.TestObserver; + +import static org.mockito.Mockito.*; + +@RunWith(RobolectricTestRunner.class) +public class OperatorCompoundButtonInputTest { + + private static CompoundButton createCompoundButton(final boolean value) { + final Activity activity = Robolectric.buildActivity(Activity.class).create().get(); + final CheckBox checkbox = new CheckBox(activity); + + checkbox.setChecked(value); + return checkbox; + } + + @Test + @SuppressWarnings("unchecked") + public void testWithoutInitialValue() { + final CompoundButton button = createCompoundButton(true); + final Observable observable = ViewObservable.input(button, false); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onNext(anyBoolean()); + + button.setChecked(true); + inOrder.verify(observer, never()).onNext(anyBoolean()); + + button.setChecked(false); + inOrder.verify(observer, times(1)).onNext(false); + + button.setChecked(true); + inOrder.verify(observer, times(1)).onNext(true); + + button.setChecked(false); + inOrder.verify(observer, times(1)).onNext(false); + subscription.unsubscribe(); + + button.setChecked(true); + inOrder.verify(observer, never()).onNext(anyBoolean()); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testWithInitialValue() { + final CompoundButton button = createCompoundButton(true); + final Observable observable = ViewObservable.input(button, true); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, times(1)).onNext(true); + + button.setChecked(false); + inOrder.verify(observer, times(1)).onNext(false); + + button.setChecked(true); + inOrder.verify(observer, times(1)).onNext(true); + + button.setChecked(true); + inOrder.verify(observer, never()).onNext(anyBoolean()); + + button.setChecked(false); + inOrder.verify(observer, times(1)).onNext(false); + subscription.unsubscribe(); + + button.setChecked(true); + inOrder.verify(observer, never()).onNext(anyBoolean()); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testMultipleSubscriptions() { + final CompoundButton button = createCompoundButton(false); + final Observable observable = ViewObservable.input(button, false); + + final Observer observer1 = mock(Observer.class); + final Observer observer2 = mock(Observer.class); + + final Subscription subscription1 = observable.subscribe(new TestObserver(observer1)); + final Subscription subscription2 = observable.subscribe(new TestObserver(observer2)); + + final InOrder inOrder1 = inOrder(observer1); + final InOrder inOrder2 = inOrder(observer2); + + button.setChecked(true); + inOrder1.verify(observer1, times(1)).onNext(true); + inOrder2.verify(observer2, times(1)).onNext(true); + + button.setChecked(false); + inOrder1.verify(observer1, times(1)).onNext(false); + inOrder2.verify(observer2, times(1)).onNext(false); + subscription1.unsubscribe(); + + button.setChecked(true); + inOrder1.verify(observer1, never()).onNext(anyBoolean()); + inOrder2.verify(observer2, times(1)).onNext(true); + subscription2.unsubscribe(); + + button.setChecked(false); + inOrder1.verify(observer1, never()).onNext(anyBoolean()); + inOrder2.verify(observer2, never()).onNext(anyBoolean()); + + inOrder1.verify(observer1, never()).onError(any(Throwable.class)); + inOrder1.verify(observer1, never()).onCompleted(); + + inOrder2.verify(observer2, never()).onError(any(Throwable.class)); + inOrder2.verify(observer2, never()).onCompleted(); + } +} diff --git a/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorEditTextInputTest.java b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorEditTextInputTest.java new file mode 100644 index 0000000000..98d34e90a5 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorEditTextInputTest.java @@ -0,0 +1,145 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.android.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import android.app.Activity; +import android.widget.EditText; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.android.observables.ViewObservable; +import rx.observers.TestObserver; + +@RunWith(RobolectricTestRunner.class) +public class OperatorEditTextInputTest { + + private static EditText createEditText(final String value) { + final Activity activity = Robolectric.buildActivity(Activity.class).create().get(); + final EditText text = new EditText(activity); + + if (value != null) { + text.setText(value); + } + + return text; + } + + @Test + @SuppressWarnings("unchecked") + public void testWithoutInitialValue() { + final EditText input = createEditText("initial"); + final Observable observable = ViewObservable.input(input, false); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onNext(anyString()); + + input.setText("1"); + inOrder.verify(observer, times(1)).onNext("1"); + + input.setText("2"); + inOrder.verify(observer, times(1)).onNext("2"); + + input.setText("3"); + inOrder.verify(observer, times(1)).onNext("3"); + + subscription.unsubscribe(); + input.setText("4"); + inOrder.verify(observer, never()).onNext(anyString()); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testWithInitialValue() { + final EditText input = createEditText("initial"); + final Observable observable = ViewObservable.input(input, true); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, times(1)).onNext("initial"); + + input.setText("one"); + inOrder.verify(observer, times(1)).onNext("one"); + + input.setText("two"); + inOrder.verify(observer, times(1)).onNext("two"); + + input.setText("three"); + inOrder.verify(observer, times(1)).onNext("three"); + + subscription.unsubscribe(); + input.setText("four"); + inOrder.verify(observer, never()).onNext(anyString()); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testMultipleSubscriptions() { + final EditText input = createEditText("initial"); + final Observable observable = ViewObservable.input(input, false); + + final Observer observer1 = mock(Observer.class); + final Observer observer2 = mock(Observer.class); + + final Subscription subscription1 = observable.subscribe(new TestObserver(observer1)); + final Subscription subscription2 = observable.subscribe(new TestObserver(observer2)); + + final InOrder inOrder1 = inOrder(observer1); + final InOrder inOrder2 = inOrder(observer2); + + input.setText("1"); + inOrder1.verify(observer1, times(1)).onNext("1"); + inOrder2.verify(observer2, times(1)).onNext("1"); + + input.setText("2"); + inOrder1.verify(observer1, times(1)).onNext("2"); + inOrder2.verify(observer2, times(1)).onNext("2"); + subscription1.unsubscribe(); + + input.setText("3"); + inOrder1.verify(observer1, never()).onNext(anyString()); + inOrder2.verify(observer2, times(1)).onNext("3"); + subscription2.unsubscribe(); + + input.setText("4"); + inOrder1.verify(observer1, never()).onNext(anyString()); + inOrder2.verify(observer2, never()).onNext(anyString()); + + inOrder1.verify(observer1, never()).onError(any(Throwable.class)); + inOrder2.verify(observer2, never()).onError(any(Throwable.class)); + + inOrder1.verify(observer1, never()).onCompleted(); + inOrder2.verify(observer2, never()).onCompleted(); + } +} diff --git a/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorViewClickTest.java b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorViewClickTest.java new file mode 100644 index 0000000000..c570e77bd6 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorViewClickTest.java @@ -0,0 +1,126 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.android.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import android.app.Activity; +import android.view.View; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.android.observables.ViewObservable; +import rx.observers.TestObserver; + +@RunWith(RobolectricTestRunner.class) +public class OperatorViewClickTest { + + @Test + @SuppressWarnings("unchecked") + public void testWithoutInitialValue() { + final View view = new View(Robolectric.buildActivity(Activity.class).create().get()); + final Observable observable = ViewObservable.clicks(view, false); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onNext(any(View.class)); + + view.performClick(); + inOrder.verify(observer, times(1)).onNext(view); + + view.performClick(); + inOrder.verify(observer, times(1)).onNext(view); + + subscription.unsubscribe(); + inOrder.verify(observer, never()).onNext(any(View.class)); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testWithInitialValue() { + final View view = new View(Robolectric.buildActivity(Activity.class).create().get()); + final Observable observable = ViewObservable.clicks(view, true); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, times(1)).onNext(view); + + view.performClick(); + inOrder.verify(observer, times(1)).onNext(view); + + view.performClick(); + inOrder.verify(observer, times(1)).onNext(view); + + subscription.unsubscribe(); + inOrder.verify(observer, never()).onNext(any(View.class)); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testMultipleSubscriptions() { + final View view = new View(Robolectric.buildActivity(Activity.class).create().get()); + final Observable observable = ViewObservable.clicks(view, false); + + final Observer observer1 = mock(Observer.class); + final Observer observer2 = mock(Observer.class); + + final Subscription subscription1 = observable.subscribe(new TestObserver(observer1)); + final Subscription subscription2 = observable.subscribe(new TestObserver(observer2)); + + final InOrder inOrder1 = inOrder(observer1); + final InOrder inOrder2 = inOrder(observer2); + + view.performClick(); + inOrder1.verify(observer1, times(1)).onNext(view); + inOrder2.verify(observer2, times(1)).onNext(view); + + view.performClick(); + inOrder1.verify(observer1, times(1)).onNext(view); + inOrder2.verify(observer2, times(1)).onNext(view); + subscription1.unsubscribe(); + + view.performClick(); + inOrder1.verify(observer1, never()).onNext(any(View.class)); + inOrder2.verify(observer2, times(1)).onNext(view); + subscription2.unsubscribe(); + + view.performClick(); + inOrder1.verify(observer1, never()).onNext(any(View.class)); + inOrder2.verify(observer2, never()).onNext(any(View.class)); + + inOrder1.verify(observer1, never()).onError(any(Throwable.class)); + inOrder2.verify(observer2, never()).onError(any(Throwable.class)); + + inOrder1.verify(observer1, never()).onCompleted(); + inOrder2.verify(observer2, never()).onCompleted(); + } +}