diff --git a/.gitignore b/.gitignore index fb5b6768..c554b8d3 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ Thumbs.db ################ *~ *.swp +local.properties # Gradle Files # ################ diff --git a/build.gradle b/build.gradle index 61772bfc..760e1aa2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,17 @@ buildscript { repositories { jcenter() } - dependencies { classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:1.12.+' } + + dependencies { classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:1.12.+' + classpath 'com.netflix.nebula:gradle-extra-configurations-plugin:1.12.+' + } } apply plugin: 'rxjava-project' - +apply plugin: 'provided-base' dependencies { compile 'io.reactivex:rxjava:1.0.+' - provided 'com.google.android:android:4.0.1.2' + provided 'com.google.android:android:4.1.1.4' provided 'com.google.android:support-v4:r7' // testing diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..8c0fb64a Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/main/java/rx/android/observables/AndroidObservable.java b/src/main/java/rx/android/observables/AndroidObservable.java index e891ded4..0648c07e 100644 --- a/src/main/java/rx/android/observables/AndroidObservable.java +++ b/src/main/java/rx/android/observables/AndroidObservable.java @@ -35,17 +35,6 @@ public final class AndroidObservable { - private static final boolean USES_SUPPORT_FRAGMENTS; - - static { - boolean supportFragmentsAvailable = false; - try { - Class.forName("android.support.v4.app.Fragment"); - supportFragmentsAvailable = true; - } catch (ClassNotFoundException e) { - } - USES_SUPPORT_FRAGMENTS = supportFragmentsAvailable; - } private static final Func1 ACTIVITY_VALIDATOR = new Func1() { @Override @@ -105,8 +94,33 @@ public static Observable fromActivity(Activity activity, Observable so * fragment while it's in detached state (i.e. its host Activity was destroyed.) In other words, during calls * to onNext, you may assume that fragment.getActivity() will never return null. *

- * This method accepts both native fragments and support library fragments in its first parameter. It will throw - * for unsupported types. + * You must unsubscribe from the returned observable in onDestroy to not leak the given fragment. + *

+ * Ex.: + *

+     *     // in any Fragment
+     *     mSubscription = fromFragment(this, Observable.just("value")).subscribe(...);
+     *     // in onDestroy
+     *     mSubscription.unsubscribe();
+     * 
+ * + * @param fragment the fragment in which the source observable will be observed + * @param sourceObservable the observable sequence to observe from the given fragment + * @param + * @return a new observable sequence that will emit notifications on the main UI thread + * @deprecated Use {@link #bindFragment(android.support.v4.app.Fragment, rx.Observable)} instead + */ + @Deprecated + public static Observable fromFragment(android.support.v4.app.Fragment fragment, Observable sourceObservable) { + Assertions.assertUiThread(); + return OperatorObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, fragment); + } + + /** + * Transforms a source observable to be attached to the given fragment, in such a way that notifications will always + * arrive on the main UI thread. Moreover, it will be guaranteed that no notifications will be delivered to the + * fragment while it's in detached state (i.e. its host Activity was destroyed.) In other words, during calls + * to onNext, you may assume that fragment.getActivity() will never return null. *

* You must unsubscribe from the returned observable in onDestroy to not leak the given fragment. *

@@ -122,18 +136,12 @@ public static Observable fromActivity(Activity activity, Observable so * @param sourceObservable the observable sequence to observe from the given fragment * @param * @return a new observable sequence that will emit notifications on the main UI thread - * @deprecated Use {@link #bindFragment(Object, rx.Observable)} instead + * @deprecated Use {@link #bindFragment(android.app.Fragment, rx.Observable)} instead */ @Deprecated - public static Observable fromFragment(Object fragment, Observable sourceObservable) { + public static Observable fromFragment(Fragment fragment, Observable sourceObservable) { Assertions.assertUiThread(); - if (USES_SUPPORT_FRAGMENTS && fragment instanceof android.support.v4.app.Fragment) { - return OperatorObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, (android.support.v4.app.Fragment) fragment); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && fragment instanceof Fragment) { - return OperatorObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, (Fragment) fragment); - } else { - throw new IllegalArgumentException("Target fragment is neither a native nor support library Fragment"); - } + return OperatorObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, fragment); } /** @@ -156,7 +164,7 @@ public static Observable bindActivity(Activity activity, Observable so } /** - * Binds the given source sequence to a fragment (native or support-v4). + * Binds the given source sequence to a support-v4 fragment. *

* This helper will schedule the given sequence to be observed on the main UI thread and ensure * that no notifications will be forwarded to the fragment in case it gets detached from its @@ -169,18 +177,30 @@ public static Observable bindActivity(Activity activity, Observable so * @param fragment the fragment to bind the source sequence to * @param source the source sequence */ - public static Observable bindFragment(Object fragment, Observable source) { + public static Observable bindFragment(android.support.v4.app.Fragment fragment, Observable source) { Assertions.assertUiThread(); final Observable o = source.observeOn(mainThread()); - if (USES_SUPPORT_FRAGMENTS && fragment instanceof android.support.v4.app.Fragment) { - android.support.v4.app.Fragment f = (android.support.v4.app.Fragment) fragment; - return o.lift(new OperatorConditionalBinding(f, FRAGMENTV4_VALIDATOR)); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && fragment instanceof Fragment) { - Fragment f = (Fragment) fragment; - return o.lift(new OperatorConditionalBinding(f, FRAGMENT_VALIDATOR)); - } else { - throw new IllegalArgumentException("Target fragment is neither a native nor support library Fragment"); - } + return o.lift(new OperatorConditionalBinding(fragment, FRAGMENTV4_VALIDATOR)); + } + + /** + * Binds the given source sequence to a native fragment. + *

+ * This helper will schedule the given sequence to be observed on the main UI thread and ensure + * that no notifications will be forwarded to the fragment in case it gets detached from its + * activity or the activity is scheduled to finish. + *

+ * You should unsubscribe from the returned Observable in onDestroy for normal fragments, or in onDestroyView + * for retained fragments, in order to not leak any references to the host activity or the fragment. + * Refer to the samples project for actual examples. + * + * @param fragment the fragment to bind the source sequence to + * @param source the source sequence + */ + public static Observable bindFragment(Fragment fragment, Observable source) { + Assertions.assertUiThread(); + final Observable o = source.observeOn(mainThread()); + return o.lift(new OperatorConditionalBinding(fragment, FRAGMENT_VALIDATOR)); } /** diff --git a/src/test/java/rx/android/observables/AndroidObservableTest.java b/src/test/java/rx/android/observables/AndroidObservableTest.java index 166ee0f3..09e88f33 100644 --- a/src/test/java/rx/android/observables/AndroidObservableTest.java +++ b/src/test/java/rx/android/observables/AndroidObservableTest.java @@ -81,11 +81,6 @@ public void itSupportsNativeFragments() { verify(observer).onCompleted(); } - @Test(expected = IllegalArgumentException.class) - public void itThrowsIfObjectPassedIsNotAFragment() { - AndroidObservable.bindFragment("not a fragment", Observable.never()); - } - @Test(expected = IllegalStateException.class) public void itThrowsIfObserverCallsFromFragmentFromBackgroundThread() throws Throwable { final Future future = Executors.newSingleThreadExecutor().submit(new Callable() { diff --git a/src/test/java/rx/android/operators/OperatorObserveFromAndroidComponentTest.java b/src/test/java/rx/android/operators/OperatorObserveFromAndroidComponentTest.java index 97f5b7b1..df0d5a1a 100644 --- a/src/test/java/rx/android/operators/OperatorObserveFromAndroidComponentTest.java +++ b/src/test/java/rx/android/operators/OperatorObserveFromAndroidComponentTest.java @@ -71,6 +71,7 @@ public void setupMocks() { } // TODO needs to be fixed, see comments inline below + /* @Ignore public void itObservesTheSourceSequenceOnTheMainUIThread() { final Observable testObservable = Observable.from(1) @@ -109,6 +110,7 @@ public void call(Integer i) { // I was going to run it on NewThread then observeOn to AndroidThread and validate it jumped // to the correct thread, but it doesn't do anything. Need to work with Android devs. } + */ @Test public void itForwardsOnNextOnCompletedSequenceToTargetObserver() {