Skip to content

Commit

Permalink
Merge pull request #1545 from ronshapiro/master
Browse files Browse the repository at this point in the history
Make Android ViewObservable.input observe TextView instead of String
  • Loading branch information
benjchristensen committed Aug 14, 2014
2 parents fa49293 + 82bc2de commit 4cb3e40
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 0 deletions.
Expand Up @@ -18,18 +18,32 @@
import rx.Observable;
import rx.operators.OperatorCompoundButtonInput;
import rx.operators.OperatorEditTextInput;
import rx.operators.OperatorTextViewInput;
import rx.operators.OperatorViewClick;

import android.view.View;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.TextView;

public class ViewObservable {

public static <T extends View> Observable<T> clicks(final T view, final boolean emitInitialValue) {
return Observable.create(new OperatorViewClick<T>(view, emitInitialValue));
}

public static <T extends TextView> Observable<T> text(final T input) {
return text(input, false);
}

public static <T extends TextView> Observable<T> text(final T input, final boolean emitInitialValue) {
return Observable.create(new OperatorTextViewInput<T>(input, emitInitialValue));
}

/**
* @deprecated Use #text(android.widget.TextView, boolean)} (and map the values) instead.
*/
@Deprecated
public static Observable<String> input(final EditText input, final boolean emitInitialValue) {
return Observable.create(new OperatorEditTextInput(input, emitInitialValue));
}
Expand Down
Expand Up @@ -25,6 +25,10 @@
import android.text.TextWatcher;
import android.widget.EditText;

/**
* @deprecated Use {@link rx.operators.OperatorTextViewInput} instead.
*/
@Deprecated
public class OperatorEditTextInput implements Observable.OnSubscribe<String> {
private final EditText input;
private final boolean emitInitialValue;
Expand Down
@@ -0,0 +1,79 @@
/**
* 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 rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.android.observables.Assertions;
import rx.android.subscriptions.AndroidSubscriptions;
import rx.functions.Action0;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.TextView;

public class OperatorTextViewInput<T extends TextView> implements Observable.OnSubscribe<T> {
private final T input;
private final boolean emitInitialValue;

public OperatorTextViewInput(final T input, final boolean emitInitialValue) {
this.input = input;
this.emitInitialValue = emitInitialValue;
}

@Override
public void call(final Subscriber<? super T> observer) {
Assertions.assertUiThread();
final TextWatcher watcher = new SimpleTextWatcher() {
@Override
public void afterTextChanged(final Editable editable) {
observer.onNext(input);
}
};

final Subscription subscription = AndroidSubscriptions.unsubscribeInUiThread(new Action0() {
@Override
public void call() {
input.removeTextChangedListener(watcher);
}
});

if (emitInitialValue) {
observer.onNext(input);
}

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
}
}
}

@@ -0,0 +1,233 @@
/**
* 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 android.widget.TextView;
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.functions.Func1;
import rx.observers.TestObserver;

@RunWith(RobolectricTestRunner.class)
public class OperatorTextViewInputTest {

private static TextView createTextView(final String value) {
final Activity activity = Robolectric.buildActivity(Activity.class).create().get();
final TextView text = new TextView(activity);

if (value != null) {
text.setText(value);
}

return text;
}

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 testOverloadedMethodDefaultsWithoutInitialValue() {
final TextView input = createTextView("initial");
final Observable<TextView> observable = ViewObservable.text(input);
final Observer<TextView> observer = mock(Observer.class);
final Subscription subscription = observable.subscribe(new TestObserver<TextView>(observer));

final InOrder inOrder = inOrder(observer);

inOrder.verify(observer, never()).onNext(any(TextView.class));

input.setText("1");
inOrder.verify(observer, times(1)).onNext(input);

input.setText("2");
inOrder.verify(observer, times(1)).onNext(input);

input.setText("3");
inOrder.verify(observer, times(1)).onNext(input);

subscription.unsubscribe();
input.setText("4");
inOrder.verify(observer, never()).onNext(any(TextView.class));

inOrder.verify(observer, never()).onError(any(Throwable.class));
inOrder.verify(observer, never()).onCompleted();
}

@Test
@SuppressWarnings("unchecked")
public void testWithoutInitialValue() {
final TextView input = createTextView("initial");
final Observable<TextView> observable = ViewObservable.text(input, false);
final Observer<TextView> observer = mock(Observer.class);
final Subscription subscription = observable.subscribe(new TestObserver<TextView>(observer));

final InOrder inOrder = inOrder(observer);

inOrder.verify(observer, never()).onNext(any(TextView.class));

input.setText("1");
inOrder.verify(observer, times(1)).onNext(input);

input.setText("2");
inOrder.verify(observer, times(1)).onNext(input);

input.setText("3");
inOrder.verify(observer, times(1)).onNext(input);

subscription.unsubscribe();
input.setText("4");
inOrder.verify(observer, never()).onNext(any(TextView.class));

inOrder.verify(observer, never()).onError(any(Throwable.class));
inOrder.verify(observer, never()).onCompleted();
}

@Test
@SuppressWarnings("unchecked")
public void testWithInitialValue() {
final TextView input = createTextView("initial");
final Observable<TextView> observable = ViewObservable.text(input, true);
final Observer<TextView> observer = mock(Observer.class);
final Subscription subscription = observable.subscribe(new TestObserver<TextView>(observer));

final InOrder inOrder = inOrder(observer);

inOrder.verify(observer, times(1)).onNext(input);

input.setText("one");
inOrder.verify(observer, times(1)).onNext(input);

input.setText("two");
inOrder.verify(observer, times(1)).onNext(input);

input.setText("three");
inOrder.verify(observer, times(1)).onNext(input);

subscription.unsubscribe();
input.setText("four");
inOrder.verify(observer, never()).onNext(any(TextView.class));

inOrder.verify(observer, never()).onError(any(Throwable.class));
inOrder.verify(observer, never()).onCompleted();
}

@Test
@SuppressWarnings("unchecked")
public void testMultipleSubscriptions() {
final TextView input = createTextView("initial");
final Observable<TextView> observable = ViewObservable.text(input, false);

final Observer<TextView> observer1 = mock(Observer.class);
final Observer<TextView> observer2 = mock(Observer.class);

final Subscription subscription1 = observable.subscribe(new TestObserver<TextView>(observer1));
final Subscription subscription2 = observable.subscribe(new TestObserver<TextView>(observer2));

final InOrder inOrder1 = inOrder(observer1);
final InOrder inOrder2 = inOrder(observer2);

input.setText("1");
inOrder1.verify(observer1, times(1)).onNext(input);
inOrder2.verify(observer2, times(1)).onNext(input);

input.setText("2");
inOrder1.verify(observer1, times(1)).onNext(input);
inOrder2.verify(observer2, times(1)).onNext(input);
subscription1.unsubscribe();

input.setText("3");
inOrder1.verify(observer1, never()).onNext(any(TextView.class));
inOrder2.verify(observer2, times(1)).onNext(input);
subscription2.unsubscribe();

input.setText("4");
inOrder1.verify(observer1, never()).onNext(any(TextView.class));
inOrder2.verify(observer2, never()).onNext(any(TextView.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();
}

@Test
@SuppressWarnings("unchecked")
public void testTextViewSubclass() {
final EditText input = createEditText("initial");
final Observable<EditText> observable = ViewObservable.text(input, false);
final Observer<EditText> observer = mock(Observer.class);
observable.subscribe(new TestObserver<EditText>(observer));

final InOrder inOrder = inOrder(observer);

inOrder.verify(observer, never()).onNext(any(EditText.class));

input.setText("1");
inOrder.verify(observer, times(1)).onNext(input);
}

@Test
@SuppressWarnings("unchecked")
public void testLegacyStringObservableCompatibility() {
final EditText input = createEditText("initial");
final Observable<String> observable = ViewObservable.text(input, false)
.map(new Func1<EditText, String>() {

@Override
public String call(EditText view) {
return view.getText().toString();
}
});
final Observer<String> observer = mock(Observer.class);
observable.subscribe(new TestObserver<String>(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");
}

}

0 comments on commit 4cb3e40

Please sign in to comment.