Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.x: Add lazy loading of default main Scheduler #338

Merged
merged 10 commits into from
Oct 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
*/
package io.reactivex.android.plugins;

import java.util.concurrent.Callable;

import io.reactivex.Scheduler;
import io.reactivex.exceptions.Exceptions;
import io.reactivex.functions.Function;
Expand All @@ -21,39 +23,38 @@
* Utility class to inject handlers to certain standard RxAndroid operations.
*/
public final class RxAndroidPlugins {
private static volatile Function<Scheduler, Scheduler> onInitMainThreadHandler;

private static volatile Function<Callable<Scheduler>, Scheduler> onInitMainThreadHandler;
private static volatile Function<Scheduler, Scheduler> onMainThreadHandler;

public static void setInitMainThreadSchedulerHandler(Function<Scheduler, Scheduler> handler) {
public static void setInitMainThreadSchedulerHandler(Function<Callable<Scheduler>, Scheduler> handler) {
onInitMainThreadHandler = handler;
}

public static Scheduler initMainThreadScheduler(Scheduler scheduler) {
Function<Scheduler, Scheduler> f = onInitMainThreadHandler;
if (f == null) {
return scheduler;
public static Scheduler initMainThreadScheduler(Callable<Scheduler> scheduler) {
if (scheduler == null) {
throw new NullPointerException("scheduler == null");
}
try {
return f.apply(scheduler);
} catch (Exception e) {
throw Exceptions.propagate(e);
Function<Callable<Scheduler>, Scheduler> f = onInitMainThreadHandler;
if (f == null) {
return callRequireNonNull(scheduler);
}
return applyRequireNonNull(f, scheduler);
}

public static void setMainThreadSchedulerHandler(Function<Scheduler, Scheduler> handler) {
onMainThreadHandler = handler;
}

public static Scheduler onMainThreadScheduler(Scheduler scheduler) {
if (scheduler == null) {
throw new NullPointerException("scheduler == null");
}
Function<Scheduler, Scheduler> f = onMainThreadHandler;
if (f == null) {
return scheduler;
}
try {
return f.apply(scheduler);
} catch (Exception e) {
throw Exceptions.propagate(e);
}
return apply(f, scheduler);
}

/**
Expand All @@ -64,6 +65,34 @@ public static void reset() {
setMainThreadSchedulerHandler(null);
}

static Scheduler callRequireNonNull(Callable<Scheduler> s) {
try {
Scheduler scheduler = s.call();
if (scheduler == null) {
throw new NullPointerException("Scheduler Callable returned null");
}
return scheduler;
} catch (Throwable ex) {
throw Exceptions.propagate(ex);
}
}

static Scheduler applyRequireNonNull(Function<Callable<Scheduler>, Scheduler> f, Callable<Scheduler> s) {
Scheduler scheduler = apply(f,s);
if (scheduler == null) {
throw new NullPointerException("Scheduler Callable returned null");
}
return scheduler;
}

static <T, R> R apply(Function<T, R> f, T t) {
try {
return f.apply(t);
} catch (Throwable ex) {
throw Exceptions.propagate(ex);
}
}

private RxAndroidPlugins() {
throw new AssertionError("No instances.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,26 @@

import android.os.Handler;
import android.os.Looper;

import java.util.concurrent.Callable;

import io.reactivex.Scheduler;
import io.reactivex.android.plugins.RxAndroidPlugins;

/** Android-specific Schedulers. */
public final class AndroidSchedulers {

private static final class MainHolder {

static final Scheduler DEFAULT = new HandlerScheduler(new Handler(Looper.getMainLooper()));
}

private static final Scheduler MAIN_THREAD = RxAndroidPlugins.initMainThreadScheduler(
new HandlerScheduler(new Handler(Looper.getMainLooper())));
new Callable<Scheduler>() {
@Override public Scheduler call() throws Exception {
return MainHolder.DEFAULT;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this indirection? Why not just new up the scheduler in here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the pattern used in RxJava which @akarnokd suggested as

Just in case call() is called multiple times by the hook, these could actually return an field of an inner class (singleton holder pattern or what's the name):

I have no strong feelings about it either way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine. Let's leave it. The indirection ensures that the classes aren't even loaded let alone run if you never call call().

}
});

/** A {@link Scheduler} which executes actions on the Android main thread. */
public static Scheduler mainThread() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,22 @@
*/
package io.reactivex.android.plugins;

import io.reactivex.Scheduler;
import io.reactivex.android.testutil.EmptyScheduler;
import io.reactivex.functions.Function;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;

import io.reactivex.Scheduler;
import io.reactivex.android.testutil.EmptyScheduler;
import io.reactivex.functions.Function;

import static junit.framework.TestCase.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

@RunWith(RobolectricTestRunner.class)
Expand Down Expand Up @@ -55,7 +60,7 @@ public void mainThreadHandlerCalled() {
public void resetClearsMainThreadHandler() {
RxAndroidPlugins.setMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override public Scheduler apply(Scheduler scheduler) {
throw new RuntimeException();
throw new AssertionError();
}
});
RxAndroidPlugins.reset();
Expand All @@ -67,32 +72,90 @@ public void resetClearsMainThreadHandler() {

@Test
public void initMainThreadHandlerCalled() {
final AtomicReference<Scheduler> schedulerRef = new AtomicReference<>();
final AtomicReference<Callable<Scheduler>> schedulerRef = new AtomicReference<>();
final Scheduler newScheduler = new EmptyScheduler();
RxAndroidPlugins.setInitMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override public Scheduler apply(Scheduler scheduler) {
schedulerRef.set(scheduler);
return newScheduler;
}
});
RxAndroidPlugins
.setInitMainThreadSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override public Scheduler apply(Callable<Scheduler> scheduler) {
schedulerRef.set(scheduler);
return newScheduler;
}
});

Scheduler scheduler = new EmptyScheduler();
Callable<Scheduler> scheduler = new Callable<Scheduler>() {
@Override public Scheduler call() throws Exception {
throw new AssertionError();
}
};
Scheduler actual = RxAndroidPlugins.initMainThreadScheduler(scheduler);
assertSame(newScheduler, actual);
assertSame(scheduler, schedulerRef.get());
}

@Test
public void resetClearsInitMainThreadHandler() {
RxAndroidPlugins.setInitMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override public Scheduler apply(Scheduler scheduler) {
throw new RuntimeException();
public void resetClearsInitMainThreadHandler() throws Exception {
RxAndroidPlugins
.setInitMainThreadSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override public Scheduler apply(Callable<Scheduler> scheduler) {
throw new AssertionError();
}
});

final Scheduler scheduler = new EmptyScheduler();
Callable<Scheduler> schedulerCallable = new Callable<Scheduler>() {
@Override public Scheduler call() throws Exception {
return scheduler;
}
});
};

RxAndroidPlugins.reset();

Scheduler scheduler = new EmptyScheduler();
Scheduler actual = RxAndroidPlugins.initMainThreadScheduler(scheduler);
assertSame(scheduler, actual);
Scheduler actual = RxAndroidPlugins.initMainThreadScheduler(schedulerCallable);
assertSame(schedulerCallable.call(), actual);
}

@Test
public void defaultMainThreadSchedulerIsInitializedLazily() {
Function<Callable<Scheduler>, Scheduler> safeOverride =
new Function<Callable<Scheduler>, Scheduler>() {
@Override public Scheduler apply(Callable<Scheduler> scheduler) {
return new EmptyScheduler();
}
};
Callable<Scheduler> unsafeDefault = new Callable<Scheduler>() {
@Override public Scheduler call() throws Exception {
throw new AssertionError();
}
};

RxAndroidPlugins.setInitMainThreadSchedulerHandler(safeOverride);
RxAndroidPlugins.initMainThreadScheduler(unsafeDefault);
}

@Test
public void overrideInitMainSchedulerThrowsWhenSchedulerCallableIsNull() {
try {
RxAndroidPlugins.initMainThreadScheduler(null);
fail();
} catch (NullPointerException e) {
assertEquals("scheduler == null", e.getMessage());
}
}

@Test
public void overrideInitMainSchedulerThrowsWhenSchedulerCallableReturnsNull() {
Callable<Scheduler> nullResultCallable = new Callable<Scheduler>() {
@Override public Scheduler call() throws Exception {
return null;
}
};

try {
RxAndroidPlugins.initMainThreadScheduler(nullResultCallable);
fail();
} catch (NullPointerException e) {
assertEquals("Scheduler Callable returned null", e.getMessage());
}
}

}