From f7fd5f1f62f57dd8a7aa6c94f91c773e637ad787 Mon Sep 17 00:00:00 2001 From: Henri Sweers Date: Sat, 8 Aug 2015 18:11:08 -0700 Subject: [PATCH 1/9] Implement view attach events + tests As brought up in #70 --- .../com/jakewharton/rxbinding/view/RxView.kt | 10 ++- rxbinding/src/androidTest/AndroidManifest.xml | 1 + .../rxbinding/view/RxViewAttachTest.java | 82 +++++++++++++++++++ .../view/RxViewAttachTestActivity.java | 18 ++++ .../jakewharton/rxbinding/view/RxView.java | 16 +++- .../rxbinding/view/ViewAttachEvent.java | 59 +++++++++++++ .../view/ViewAttachEventOnSubscribe.java | 47 +++++++++++ 7 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java create mode 100644 rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTestActivity.java create mode 100644 rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java create mode 100644 rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEventOnSubscribe.java diff --git a/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt b/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt index 1235ac83..07586f53 100644 --- a/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt +++ b/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt @@ -3,12 +3,20 @@ package com.jakewharton.rxbinding.view import android.view.DragEvent import android.view.MotionEvent import android.view.View -import rx.Observable import com.jakewharton.rxbinding.internal.Functions +import rx.Observable import rx.functions.Action1 import rx.functions.Func0 import rx.functions.Func1 +/** + * Create an observable of attach and detach events on `view`. + * + * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe + * to free this reference. + */ +public inline fun View.attachEvents(): Observable = RxView.attachEvents(this) + /** * Create an observable of timestamps for clicks on `view`. * diff --git a/rxbinding/src/androidTest/AndroidManifest.xml b/rxbinding/src/androidTest/AndroidManifest.xml index ff8ac693..8d8cb78a 100644 --- a/rxbinding/src/androidTest/AndroidManifest.xml +++ b/rxbinding/src/androidTest/AndroidManifest.xml @@ -4,6 +4,7 @@ package="com.jakewharton.rxbinding"> + diff --git a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java new file mode 100644 index 00000000..591fa828 --- /dev/null +++ b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java @@ -0,0 +1,82 @@ +package com.jakewharton.rxbinding.view; + +import android.app.Instrumentation; +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.Espresso; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; +import android.widget.FrameLayout; + +import com.jakewharton.rxbinding.RecordingObserver; +import com.jakewharton.rxbinding.ViewDirtyIdlingResource; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; + +import static com.google.common.truth.Truth.assertThat; +import static com.jakewharton.rxbinding.view.ViewAttachEvent.ATTACH; +import static com.jakewharton.rxbinding.view.ViewAttachEvent.DETACH; + +@RunWith(AndroidJUnit4.class) +public final class RxViewAttachTest { + @Rule public final ActivityTestRule activityRule = + new ActivityTestRule<>(RxViewAttachTestActivity.class); + + private final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + private FrameLayout parent; + private View child; + private ViewDirtyIdlingResource viewDirtyIdler; + + @Before public void setUp() { + RxViewAttachTestActivity activity = activityRule.getActivity(); + parent = activity.parent; + child = activity.child; + viewDirtyIdler = new ViewDirtyIdlingResource(activity); + Espresso.registerIdlingResources(viewDirtyIdler); + } + + @After public void tearDown() { + Espresso.unregisterIdlingResources(viewDirtyIdler); + } + + @Test public void attachEvents() { + RecordingObserver o = new RecordingObserver<>(); + Subscription subscription = RxView.attachEvents(child) + .subscribeOn(AndroidSchedulers.mainThread()) + .subscribe(o); + o.assertNoMoreEvents(); // No initial value. + + instrumentation.runOnMainSync(new Runnable() { + @Override public void run() { + parent.addView(child); + } + }); + instrumentation.waitForIdleSync(); + assertThat(o.takeNext().kind()).isEqualTo(ATTACH); + instrumentation.runOnMainSync(new Runnable() { + @Override public void run() { + parent.removeView(child); + } + }); + instrumentation.waitForIdleSync(); + assertThat(o.takeNext().kind()).isEqualTo(DETACH); + + subscription.unsubscribe(); + + instrumentation.runOnMainSync(new Runnable() { + @Override public void run() { + parent.addView(child); + parent.removeView(child); + } + }); + instrumentation.waitForIdleSync(); + o.assertNoMoreEvents(); + } +} diff --git a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTestActivity.java b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTestActivity.java new file mode 100644 index 00000000..b925b8e3 --- /dev/null +++ b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTestActivity.java @@ -0,0 +1,18 @@ +package com.jakewharton.rxbinding.view; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.widget.FrameLayout; + +public final class RxViewAttachTestActivity extends Activity { + FrameLayout parent; + View child; + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + parent = new FrameLayout(this); + child = new View(this); + setContentView(parent); + } +} diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java index 88fc1812..6ec3bbf9 100644 --- a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java @@ -4,8 +4,10 @@ import android.view.DragEvent; import android.view.MotionEvent; import android.view.View; -import rx.Observable; + import com.jakewharton.rxbinding.internal.Functions; + +import rx.Observable; import rx.functions.Action1; import rx.functions.Func0; import rx.functions.Func1; @@ -18,6 +20,18 @@ * actions} for {@link View}. */ public final class RxView { + /** + * Create an observable of attach and detach events on {@code view}. + *

+ * Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe + * to free this reference. + */ + @CheckResult + public static Observable attachEvents(View view) { + checkNotNull(view, "view == null"); + return Observable.create(new ViewAttachEventOnSubscribe(view)); + } + /** * Create an observable of timestamps for clicks on {@code view}. *

diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java new file mode 100644 index 00000000..99da7c04 --- /dev/null +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java @@ -0,0 +1,59 @@ +package com.jakewharton.rxbinding.view; + +import android.content.Context; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.view.View; + +/** + * A view attach event on a view. + *

+ * Warning: Instances keep a strong reference to the view. Operators that + * cache instances have the potential to leak the associated {@link Context}. + */ +public final class ViewAttachEvent extends ViewEvent { + + public static final int ATTACH = 0; + public static final int DETACH = 1; + + @IntDef({ATTACH, DETACH}) + public @interface Kind {} + + public static ViewAttachEvent create(@NonNull View view, @Kind int kind) { + return new ViewAttachEvent(view, kind); + } + + @Kind private final int kind; + + private ViewAttachEvent(View view, @Kind int kind) { + super(view); + this.kind = kind; + } + + @Kind public int kind() { + return kind; + } + + @Override public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ViewAttachEvent)) return false; + ViewAttachEvent other = (ViewAttachEvent) o; + return other.view() == view() + && kind() == other.kind(); + } + + @Override public int hashCode() { + int result = 17; + result = result * 37 + view().hashCode(); + result = result * 37 + kind(); + return result; + } + + @Override public String toString() { + return "ViewAttachEvent{view=" + + view() + + ", kind=" + + (kind == ATTACH ? "ATTACH" : "DETACH") + + '}'; + } +} diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEventOnSubscribe.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEventOnSubscribe.java new file mode 100644 index 00000000..15981817 --- /dev/null +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEventOnSubscribe.java @@ -0,0 +1,47 @@ +package com.jakewharton.rxbinding.view; + +import android.support.annotation.NonNull; +import android.view.View; + +import com.jakewharton.rxbinding.internal.MainThreadSubscription; + +import rx.Observable; +import rx.Subscriber; + +import static com.jakewharton.rxbinding.internal.Preconditions.checkUiThread; +import static com.jakewharton.rxbinding.view.ViewAttachEvent.ATTACH; +import static com.jakewharton.rxbinding.view.ViewAttachEvent.DETACH; + +final class ViewAttachEventOnSubscribe implements Observable.OnSubscribe { + private final View view; + + ViewAttachEventOnSubscribe(View view) { + this.view = view; + } + + @Override public void call(final Subscriber subscriber) { + checkUiThread(); + + final View.OnAttachStateChangeListener listener = new View.OnAttachStateChangeListener() { + @Override public void onViewAttachedToWindow(@NonNull final View v) { + if (!subscriber.isUnsubscribed()) { + subscriber.onNext(ViewAttachEvent.create(view, ATTACH)); + } + } + + @Override public void onViewDetachedFromWindow(@NonNull final View v) { + if (!subscriber.isUnsubscribed()) { + subscriber.onNext(ViewAttachEvent.create(view, DETACH)); + } + } + }; + + subscriber.add(new MainThreadSubscription() { + @Override protected void onUnsubscribe() { + view.removeOnAttachStateChangeListener(listener); + } + }); + + view.addOnAttachStateChangeListener(listener); + } +} From 0114616e0fc5c4eca0f5bb3e3fbcb1a9629526a5 Mon Sep 17 00:00:00 2001 From: Henri Sweers Date: Sat, 8 Aug 2015 18:55:14 -0700 Subject: [PATCH 2/9] Use enum instead of IntDef --- .../rxbinding/view/RxViewAttachTest.java | 4 ++-- .../rxbinding/view/ViewAttachEvent.java | 21 ++++++++----------- .../view/ViewAttachEventOnSubscribe.java | 4 ++-- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java index 591fa828..078a3d00 100644 --- a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java +++ b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java @@ -21,8 +21,8 @@ import rx.android.schedulers.AndroidSchedulers; import static com.google.common.truth.Truth.assertThat; -import static com.jakewharton.rxbinding.view.ViewAttachEvent.ATTACH; -import static com.jakewharton.rxbinding.view.ViewAttachEvent.DETACH; +import static com.jakewharton.rxbinding.view.ViewAttachEvent.Kind.ATTACH; +import static com.jakewharton.rxbinding.view.ViewAttachEvent.Kind.DETACH; @RunWith(AndroidJUnit4.class) public final class RxViewAttachTest { diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java index 99da7c04..f068c34d 100644 --- a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java @@ -1,7 +1,6 @@ package com.jakewharton.rxbinding.view; import android.content.Context; -import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.view.View; @@ -13,24 +12,22 @@ */ public final class ViewAttachEvent extends ViewEvent { - public static final int ATTACH = 0; - public static final int DETACH = 1; - - @IntDef({ATTACH, DETACH}) - public @interface Kind {} + public enum Kind { + ATTACH, DETACH + } - public static ViewAttachEvent create(@NonNull View view, @Kind int kind) { + public static ViewAttachEvent create(@NonNull View view, Kind kind) { return new ViewAttachEvent(view, kind); } - @Kind private final int kind; + private final Kind kind; - private ViewAttachEvent(View view, @Kind int kind) { + private ViewAttachEvent(View view, Kind kind) { super(view); this.kind = kind; } - @Kind public int kind() { + public Kind kind() { return kind; } @@ -45,7 +42,7 @@ private ViewAttachEvent(View view, @Kind int kind) { @Override public int hashCode() { int result = 17; result = result * 37 + view().hashCode(); - result = result * 37 + kind(); + result = result * 37 + kind().hashCode(); return result; } @@ -53,7 +50,7 @@ private ViewAttachEvent(View view, @Kind int kind) { return "ViewAttachEvent{view=" + view() + ", kind=" - + (kind == ATTACH ? "ATTACH" : "DETACH") + + kind() + '}'; } } diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEventOnSubscribe.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEventOnSubscribe.java index 15981817..a0d2ee70 100644 --- a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEventOnSubscribe.java +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEventOnSubscribe.java @@ -9,8 +9,8 @@ import rx.Subscriber; import static com.jakewharton.rxbinding.internal.Preconditions.checkUiThread; -import static com.jakewharton.rxbinding.view.ViewAttachEvent.ATTACH; -import static com.jakewharton.rxbinding.view.ViewAttachEvent.DETACH; +import static com.jakewharton.rxbinding.view.ViewAttachEvent.Kind.ATTACH; +import static com.jakewharton.rxbinding.view.ViewAttachEvent.Kind.DETACH; final class ViewAttachEventOnSubscribe implements Observable.OnSubscribe { private final View view; From 03782e1600be9e976b2dff5933d889e8a031c6fc Mon Sep 17 00:00:00 2001 From: Henri Sweers Date: Sat, 8 Aug 2015 18:55:59 -0700 Subject: [PATCH 3/9] Remove unused espresso stuff --- .../com/jakewharton/rxbinding/view/RxViewAttachTest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java index 078a3d00..a5dc1935 100644 --- a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java +++ b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java @@ -2,14 +2,12 @@ import android.app.Instrumentation; import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.Espresso; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.widget.FrameLayout; import com.jakewharton.rxbinding.RecordingObserver; -import com.jakewharton.rxbinding.ViewDirtyIdlingResource; import org.junit.After; import org.junit.Before; @@ -32,18 +30,14 @@ public final class RxViewAttachTest { private final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); private FrameLayout parent; private View child; - private ViewDirtyIdlingResource viewDirtyIdler; @Before public void setUp() { RxViewAttachTestActivity activity = activityRule.getActivity(); parent = activity.parent; child = activity.child; - viewDirtyIdler = new ViewDirtyIdlingResource(activity); - Espresso.registerIdlingResources(viewDirtyIdler); } @After public void tearDown() { - Espresso.unregisterIdlingResources(viewDirtyIdler); } @Test public void attachEvents() { From 6eb1f1db3efd7a10454bb61cec449bc2b26de8b3 Mon Sep 17 00:00:00 2001 From: Henri Sweers Date: Sat, 8 Aug 2015 19:20:13 -0700 Subject: [PATCH 4/9] Remove empty teardown method --- .../java/com/jakewharton/rxbinding/view/RxViewAttachTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java index a5dc1935..c7cfdc1f 100644 --- a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java +++ b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java @@ -9,7 +9,6 @@ import com.jakewharton.rxbinding.RecordingObserver; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -37,9 +36,6 @@ public final class RxViewAttachTest { child = activity.child; } - @After public void tearDown() { - } - @Test public void attachEvents() { RecordingObserver o = new RecordingObserver<>(); Subscription subscription = RxView.attachEvents(child) From bb30a2332b78c94febd1b315fe2e69a57ac0c032 Mon Sep 17 00:00:00 2001 From: Henri Sweers Date: Sat, 8 Aug 2015 19:24:57 -0700 Subject: [PATCH 5/9] Implement attaches/detaches siblings to attachEvents() --- .../com/jakewharton/rxbinding/view/RxView.kt | 16 +++++ .../rxbinding/view/RxViewAttachTest.java | 68 +++++++++++++++++++ .../jakewharton/rxbinding/view/RxView.java | 22 ++++++ .../view/ViewAttachesOnSubscribe.java | 48 +++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachesOnSubscribe.java diff --git a/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt b/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt index 07586f53..8c63916c 100644 --- a/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt +++ b/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt @@ -9,6 +9,14 @@ import rx.functions.Action1 import rx.functions.Func0 import rx.functions.Func1 +/** + * Create an observable of timestamps for `view`'s attaches. + * + * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe + * to free this reference. + */ +public inline fun View.attaches(): Observable = RxView.attaches(this) + /** * Create an observable of attach and detach events on `view`. * @@ -17,6 +25,14 @@ import rx.functions.Func1 */ public inline fun View.attachEvents(): Observable = RxView.attachEvents(this) +/** + * Create an observable of timestamps for `view`'s detaches. + * + * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe + * to free this reference. + */ +public inline fun View.detaches(): Observable = RxView.detaches(this) + /** * Create an observable of timestamps for clicks on `view`. * diff --git a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java index c7cfdc1f..84931b5e 100644 --- a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java +++ b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java @@ -36,6 +36,40 @@ public final class RxViewAttachTest { child = activity.child; } + @Test public void attaches() { + RecordingObserver o = new RecordingObserver<>(); + Subscription subscription = RxView.attaches(child) + .subscribeOn(AndroidSchedulers.mainThread()) + .subscribe(o); + o.assertNoMoreEvents(); // No initial value. + + instrumentation.runOnMainSync(new Runnable() { + @Override public void run() { + parent.addView(child); + } + }); + instrumentation.waitForIdleSync(); + assertThat(o.takeNext()).isNotNull(); + instrumentation.runOnMainSync(new Runnable() { + @Override public void run() { + parent.removeView(child); + } + }); + instrumentation.waitForIdleSync(); + o.assertNoMoreEvents(); + + subscription.unsubscribe(); + + instrumentation.runOnMainSync(new Runnable() { + @Override public void run() { + parent.addView(child); + parent.removeView(child); + } + }); + instrumentation.waitForIdleSync(); + o.assertNoMoreEvents(); + } + @Test public void attachEvents() { RecordingObserver o = new RecordingObserver<>(); Subscription subscription = RxView.attachEvents(child) @@ -69,4 +103,38 @@ public final class RxViewAttachTest { instrumentation.waitForIdleSync(); o.assertNoMoreEvents(); } + + @Test public void detaches() { + RecordingObserver o = new RecordingObserver<>(); + Subscription subscription = RxView.detaches(child) + .subscribeOn(AndroidSchedulers.mainThread()) + .subscribe(o); + o.assertNoMoreEvents(); // No initial value. + + instrumentation.runOnMainSync(new Runnable() { + @Override public void run() { + parent.addView(child); + } + }); + instrumentation.waitForIdleSync(); + o.assertNoMoreEvents(); + instrumentation.runOnMainSync(new Runnable() { + @Override public void run() { + parent.removeView(child); + } + }); + instrumentation.waitForIdleSync(); + assertThat(o.takeNext()).isNotNull(); + + subscription.unsubscribe(); + + instrumentation.runOnMainSync(new Runnable() { + @Override public void run() { + parent.addView(child); + parent.removeView(child); + } + }); + instrumentation.waitForIdleSync(); + o.assertNoMoreEvents(); + } } diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java index 6ec3bbf9..886f52de 100644 --- a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java @@ -20,6 +20,17 @@ * actions} for {@link View}. */ public final class RxView { + /** + * Create an observable of timestamps for {@code view}'s attaches. + *

+ * Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe + * to free this reference. + */ + public static Observable attaches(View view) { + checkNotNull(view, "view == null"); + return Observable.create(new ViewAttachesOnSubscribe(view, true)); + } + /** * Create an observable of attach and detach events on {@code view}. *

@@ -32,6 +43,17 @@ public static Observable attachEvents(View view) { return Observable.create(new ViewAttachEventOnSubscribe(view)); } + /** + * Create an observable of timestamps for {@code view}'s detaches. + *

+ * Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe + * to free this reference. + */ + public static Observable detaches(View view) { + checkNotNull(view, "view == null"); + return Observable.create(new ViewAttachesOnSubscribe(view, false)); + } + /** * Create an observable of timestamps for clicks on {@code view}. *

diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachesOnSubscribe.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachesOnSubscribe.java new file mode 100644 index 00000000..c9c57529 --- /dev/null +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachesOnSubscribe.java @@ -0,0 +1,48 @@ +package com.jakewharton.rxbinding.view; + +import android.support.annotation.NonNull; +import android.view.View; + +import com.jakewharton.rxbinding.internal.MainThreadSubscription; + +import rx.Observable; +import rx.Subscriber; + +import static com.jakewharton.rxbinding.internal.Preconditions.checkUiThread; + +final class ViewAttachesOnSubscribe implements Observable.OnSubscribe { + private final Object event = new Object(); + private final boolean callOnAttach; + private final View view; + + ViewAttachesOnSubscribe(View view, boolean callOnAttach) { + this.view = view; + this.callOnAttach = callOnAttach; + } + + @Override public void call(final Subscriber subscriber) { + checkUiThread(); + + final View.OnAttachStateChangeListener listener = new View.OnAttachStateChangeListener() { + @Override public void onViewAttachedToWindow(@NonNull final View v) { + if (callOnAttach && !subscriber.isUnsubscribed()) { + subscriber.onNext(event); + } + } + + @Override public void onViewDetachedFromWindow(@NonNull final View v) { + if (!callOnAttach && !subscriber.isUnsubscribed()) { + subscriber.onNext(event); + } + } + }; + + subscriber.add(new MainThreadSubscription() { + @Override protected void onUnsubscribe() { + view.removeOnAttachStateChangeListener(listener); + } + }); + + view.addOnAttachStateChangeListener(listener); + } +} From c186320a5077781a98e7a8f5f12e3d65406d6691 Mon Sep 17 00:00:00 2001 From: Henri Sweers Date: Sat, 8 Aug 2015 20:01:19 -0700 Subject: [PATCH 6/9] Remove unnecessary idle syncs --- .../com/jakewharton/rxbinding/view/RxViewAttachTest.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java index 84931b5e..46f4f91b 100644 --- a/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java +++ b/rxbinding/src/androidTest/java/com/jakewharton/rxbinding/view/RxViewAttachTest.java @@ -48,14 +48,12 @@ public final class RxViewAttachTest { parent.addView(child); } }); - instrumentation.waitForIdleSync(); assertThat(o.takeNext()).isNotNull(); instrumentation.runOnMainSync(new Runnable() { @Override public void run() { parent.removeView(child); } }); - instrumentation.waitForIdleSync(); o.assertNoMoreEvents(); subscription.unsubscribe(); @@ -66,7 +64,6 @@ public final class RxViewAttachTest { parent.removeView(child); } }); - instrumentation.waitForIdleSync(); o.assertNoMoreEvents(); } @@ -82,14 +79,12 @@ public final class RxViewAttachTest { parent.addView(child); } }); - instrumentation.waitForIdleSync(); assertThat(o.takeNext().kind()).isEqualTo(ATTACH); instrumentation.runOnMainSync(new Runnable() { @Override public void run() { parent.removeView(child); } }); - instrumentation.waitForIdleSync(); assertThat(o.takeNext().kind()).isEqualTo(DETACH); subscription.unsubscribe(); @@ -100,7 +95,6 @@ public final class RxViewAttachTest { parent.removeView(child); } }); - instrumentation.waitForIdleSync(); o.assertNoMoreEvents(); } @@ -116,14 +110,12 @@ public final class RxViewAttachTest { parent.addView(child); } }); - instrumentation.waitForIdleSync(); o.assertNoMoreEvents(); instrumentation.runOnMainSync(new Runnable() { @Override public void run() { parent.removeView(child); } }); - instrumentation.waitForIdleSync(); assertThat(o.takeNext()).isNotNull(); subscription.unsubscribe(); @@ -134,7 +126,6 @@ public final class RxViewAttachTest { parent.removeView(child); } }); - instrumentation.waitForIdleSync(); o.assertNoMoreEvents(); } } From 7e078b8537af57c2209ea00d0d256f60aed05cd3 Mon Sep 17 00:00:00 2001 From: Henri Sweers Date: Sat, 8 Aug 2015 20:01:52 -0700 Subject: [PATCH 7/9] Flip == to match --- .../java/com/jakewharton/rxbinding/view/ViewAttachEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java index f068c34d..ad5cf61d 100644 --- a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewAttachEvent.java @@ -36,7 +36,7 @@ public Kind kind() { if (!(o instanceof ViewAttachEvent)) return false; ViewAttachEvent other = (ViewAttachEvent) o; return other.view() == view() - && kind() == other.kind(); + && other.kind() == kind(); } @Override public int hashCode() { From 296b5863f57f68636df52b108a0e91e020a63e8b Mon Sep 17 00:00:00 2001 From: Henri Sweers Date: Sat, 8 Aug 2015 20:12:05 -0700 Subject: [PATCH 8/9] More accurate docs on attaches/detaches --- .../main/kotlin/com/jakewharton/rxbinding/view/RxView.kt | 6 ++++-- .../main/java/com/jakewharton/rxbinding/view/RxView.java | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt b/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt index 8c63916c..a586e991 100644 --- a/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt +++ b/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt @@ -10,7 +10,8 @@ import rx.functions.Func0 import rx.functions.Func1 /** - * Create an observable of timestamps for `view`'s attaches. + * Create an observable which emits on `view` attach events. The emitted value is + * unspecified and should only be used as notification. * * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe * to free this reference. @@ -26,7 +27,8 @@ public inline fun View.attaches(): Observable = RxView.attaches(this) public inline fun View.attachEvents(): Observable = RxView.attachEvents(this) /** - * Create an observable of timestamps for `view`'s detaches. + * Create an observable which emits on `view` detach events. The emitted value is + * unspecified and should only be used as notification. * * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe * to free this reference. diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java index 886f52de..4a6a65c4 100644 --- a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java @@ -21,7 +21,8 @@ */ public final class RxView { /** - * Create an observable of timestamps for {@code view}'s attaches. + * Create an observable which emits on {@code view} attach events. The emitted value is + * unspecified and should only be used as notification. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. @@ -44,7 +45,8 @@ public static Observable attachEvents(View view) { } /** - * Create an observable of timestamps for {@code view}'s detaches. + * Create an observable which emits on {@code view} detach events. The emitted value is + * unspecified and should only be used as notification. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. From d6cd19ce17a0a2c34e1b2b4b026bc7ea10df8667 Mon Sep 17 00:00:00 2001 From: Henri Sweers Date: Sat, 8 Aug 2015 20:16:26 -0700 Subject: [PATCH 9/9] Update other "timestamp"-style docs with more accurate description --- .../main/kotlin/com/jakewharton/rxbinding/view/RxView.kt | 9 ++++++--- .../main/java/com/jakewharton/rxbinding/view/RxView.java | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt b/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt index a586e991..f625ef22 100644 --- a/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt +++ b/rxbinding-kotlin/src/main/kotlin/com/jakewharton/rxbinding/view/RxView.kt @@ -36,7 +36,8 @@ public inline fun View.attachEvents(): Observable = RxView.atta public inline fun View.detaches(): Observable = RxView.detaches(this) /** - * Create an observable of timestamps for clicks on `view`. + * Create an observable which emits on `view` click events. The emitted value is + * unspecified and should only be used as notification. * * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe * to free this reference. @@ -134,7 +135,8 @@ public inline fun View.focusChanges(): Observable = RxView.focusChanges public inline fun View.focusChangeEvents(): Observable = RxView.focusChangeEvents(this) /** - * Create an observable of timestamps for long-clicks on `view`. + * Create an observable which emits on `view` long-click events. The emitted value is + * unspecified and should only be used as notification. * * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe * to free this reference. @@ -145,7 +147,8 @@ public inline fun View.focusChangeEvents(): Observable = R public inline fun View.longClicks(): Observable = RxView.longClicks(this) /** - * Create an observable of timestamps for clicks on `view`. + * Create an observable which emits on `view` long-click events. The emitted value is + * unspecified and should only be used as notification. * * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe * to free this reference. diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java index 4a6a65c4..d87396c5 100644 --- a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxView.java @@ -57,7 +57,8 @@ public static Observable detaches(View view) { } /** - * Create an observable of timestamps for clicks on {@code view}. + * Create an observable which emits on {@code view} click events. The emitted value is + * unspecified and should only be used as notification. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. @@ -191,7 +192,8 @@ public static Observable focusChangeEvents(View view) { } /** - * Create an observable of timestamps for long-clicks on {@code view}. + * Create an observable which emits on {@code view} long-click events. The emitted value is + * unspecified and should only be used as notification. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. @@ -206,7 +208,8 @@ public static Observable longClicks(View view) { } /** - * Create an observable of timestamps for clicks on {@code view}. + * Create an observable which emits on {@code view} long-click events. The emitted value is + * unspecified and should only be used as notification. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference.