diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/AndroidObservable.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/AndroidObservable.java index c70ba970c3..5ae8a11a74 100644 --- a/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/AndroidObservable.java +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/AndroidObservable.java @@ -15,26 +15,145 @@ */ package rx.android.observables; +import static org.mockito.Mockito.verify; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import rx.Observable; +import rx.Observer; import rx.operators.OperationObserveFromAndroidComponent; import android.app.Activity; import android.app.Fragment; +import android.os.Build; +import android.support.v4.app.FragmentActivity; + 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 AndroidObservable() {} + /** + * Transforms a source observable to be attached to the given Activity, in such a way that notifications will always + * arrive on the main UI thread. Currently, this is equivalent to calling observeOn(AndroidSchedulers.mainThread()), + * but this behavior may change in the future, so it is encouraged to use this wrapper instead. + *

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

+ * Ex.: + *

+     *     // in any Activity
+     *     mSubscription = fromActivity(this, Observable.just("value")).subscribe(...);
+     *     // in onDestroy
+     *     mSubscription.unsubscribe();
+     * 
+ * + * @param activity the activity in which the source observable will be observed + * @param sourceObservable the observable sequence to observe from the given Activity + * @param + * @return a new observable sequence that will emit notifications on the main UI thread + */ public static Observable fromActivity(Activity activity, Observable sourceObservable) { return OperationObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, activity); } - public static Observable fromFragment(Fragment fragment, Observable sourceObservable) { - return OperationObserveFromAndroidComponent.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. + *

+ * 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 + */ + public static Observable fromFragment(Object fragment, Observable sourceObservable) { + if (USES_SUPPORT_FRAGMENTS && fragment instanceof android.support.v4.app.Fragment) { + return OperationObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, (android.support.v4.app.Fragment) fragment); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && fragment instanceof Fragment) { + return OperationObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, (Fragment) fragment); + } else { + throw new IllegalArgumentException("Target fragment is neither a native nor support library Fragment"); + } } - public static Observable fromFragment(android.support.v4.app.Fragment fragment, Observable sourceObservable) { - return OperationObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, fragment); + @RunWith(RobolectricTestRunner.class) + @Config(manifest = Config.NONE) + public static final class AndroidObservableTest { + + // support library fragments + private FragmentActivity fragmentActivity; + private android.support.v4.app.Fragment supportFragment; + + // native fragments + private Activity activity; + private Fragment fragment; + + @Mock + private Observer observer; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + supportFragment = new android.support.v4.app.Fragment(); + fragmentActivity = Robolectric.buildActivity(FragmentActivity.class).create().get(); + fragmentActivity.getSupportFragmentManager().beginTransaction().add(supportFragment, null).commit(); + + fragment = new Fragment(); + activity = Robolectric.buildActivity(Activity.class).create().get(); + activity.getFragmentManager().beginTransaction().add(fragment, null).commit(); + } + + @Test + public void itSupportsFragmentsFromTheSupportV4Library() { + fromFragment(supportFragment, Observable.just("success")).subscribe(observer); + verify(observer).onNext("success"); + verify(observer).onCompleted(); + } + + @Test + public void itSupportsNativeFragments() { + fromFragment(fragment, Observable.just("success")).subscribe(observer); + verify(observer).onNext("success"); + verify(observer).onCompleted(); + } + + @Test(expected = IllegalArgumentException.class) + public void itThrowsIfObjectPassedIsNotAFragment() { + fromFragment("not a fragment", Observable.never()); + } } }