/** * Creates a {@link Transformer} that will flatten the provided {@link Action0} into the stream as * a {@link Completable} every time it receives an effect from the upstream effects observable. * This will result in calling the provided Action every time an effect is dispatched to the * created transformer. * * @param doEffect {@link Action0} to be run every time the effect is requested * @param <F> the type of Effect this transformer handles * @param <E> these transformers are for effects that do not result in any events; however, they * still need to share the same Event type * @return a {@link Transformer} that can be used with an {@link SubtypeEffectHandlerBuilder} */ static <F, E> Transformer<F, E> fromAction(final Action0 doEffect) { return fromAction(doEffect, null); }
/** * Creates an {@link Transformer} that will flatten the provided {@link Action1} into the stream * as a {@link Completable} every time it receives an effect from the upstream effects observable. * This will result in calling the consumer and passing it the requested effect object. * * @param doEffect {@link Action1} to be run every time the effect is requested * @param <F> the type of Effect this transformer handles * @param <E> these transformers are for effects that do not result in any events; however, they * still need to share the same Event type * @return a {@link Transformer} that can be used with an {@link SubtypeEffectHandlerBuilder} */ static <F, E> Transformer<F, E> fromConsumer(final Action1<F> doEffect) { return fromConsumer(doEffect, null); }
/** * Creates an {@link Observable.Transformer} that will flatten the provided {@link Func1} into the * stream as an {@link Observable} every time it receives an effect from the upstream effects * observable. This will result in calling the function on the immediate scheduler, and passing it * the requested effect object then emitting its returned value. * * @param function {@link Func1} to be invoked every time the effect is requested * @param <F> the type of Effect this transformer handles * @param <E> the type of Event this transformer emits * @return an {@link Observable.Transformer} that can be used with a {@link * SubtypeEffectHandlerBuilder}. */ static <F, E> Observable.Transformer<F, E> fromFunction(final Function<F, E> function) { return fromFunction(function, null); } }
/** * Creates an {@link Transformer} that will flatten the provided {@link Action0} into the stream * as a {@link Completable} every time it receives an effect from the upstream effects observable. * This Completable will be subscribed on the specified {@link Scheduler}. This will result in * calling the provided Action on the specified scheduler every time an effect dispatched to the * created effect transformer. * * @param doEffect {@link Action0} to be run every time the effect is requested * @param scheduler the {@link Scheduler} that the action should be run on * @param <F> the type of Effect this transformer handles * @param <E> these transformers are for effects that do not result in any events; however, they * still need to share the same Event type * @return a {@link Transformer} that can be used with an {@link SubtypeEffectHandlerBuilder} */ static <F, E> Transformer<F, E> fromAction( final Action0 doEffect, @Nullable final Scheduler scheduler) { return fromConsumer( new Action1<F>() { @Override public void call(F f) { try { doEffect.call(); } catch (Exception e) { throw OnErrorThrowable.from(e); } } }, scheduler); }
/** * Add a {@link Func1} for handling effects of a given type. The function will be invoked once * for every received effect object that extends the given class. The returned event will be * forwarded to the Mobius loop. * * <p>Adding handlers for two effect classes where one is a super-class of the other is * considered a collision and is not allowed. Registering the same class twice is also * considered a collision. * * @param effectClass the class to handle * @param function the function that should be invoked for the effect * @param <G> the effect class as a type parameter * @return this builder * @throws IllegalArgumentException if there is a handler collision */ public <G extends F> SubtypeEffectHandlerBuilder<F, E> addFunction( final Class<G> effectClass, final Function<G, E> function) { //noinspection ResultOfMethodCallIgnored checkNotNull(effectClass); //noinspection ResultOfMethodCallIgnored checkNotNull(function); return addTransformer(effectClass, Transformers.fromFunction(function)); }
/** * Add an {@link Action0} for handling effects of a given type. The action will be invoked once * for every received effect object that extends the given class. * * <p>Adding handlers for two effect classes where one is a super-class of the other is * considered a collision and is not allowed. Registering the same class twice is also * considered a collision. * * @param effectClass the class to handle * @param action the action that should be invoked for the effect * @param scheduler the scheduler that should be used to invoke the action * @param <G> the effect class as a type parameter * @return this builder * @throws IllegalArgumentException if there is a handler collision */ public <G extends F> SubtypeEffectHandlerBuilder<F, E> addAction( final Class<G> effectClass, final Action0 action, Scheduler scheduler) { //noinspection ResultOfMethodCallIgnored checkNotNull(effectClass); //noinspection ResultOfMethodCallIgnored checkNotNull(action); return addTransformer(effectClass, Transformers.<G, E>fromAction(action, scheduler)); }
/** * Add an {@link Action1} for handling effects of a given type. The action will be invoked once * for every received effect object that extends the given class. * * <p>Adding handlers for two effect classes where one is a super-class of the other is * considered a collision and is not allowed. Registering the same class twice is also * considered a collision. * * @param effectClass the class to handle * @param consumer the consumer that should be invoked for the effect * @param <G> the effect class as a type parameter * @return this builder * @throws IllegalArgumentException if there is a handler collision */ public <G extends F> SubtypeEffectHandlerBuilder<F, E> addConsumer( final Class<G> effectClass, final Action1<G> consumer) { //noinspection ResultOfMethodCallIgnored checkNotNull(effectClass); //noinspection ResultOfMethodCallIgnored checkNotNull(consumer); return addTransformer(effectClass, Transformers.<G, E>fromConsumer(consumer)); }
/** * Add a {@link Func1} for handling effects of a given type. The function will be invoked once * for every received effect object that extends the given class. The returned event will be * forwarded to the Mobius loop. * * <p>Adding handlers for two effect classes where one is a super-class of the other is * considered a collision and is not allowed. Registering the same class twice is also * considered a collision. * * @param effectClass the class to handle * @param function the function that should be invoked for the effect * @param scheduler the scheduler that should be used when invoking the function * @param <G> the effect class as a type parameter * @return this builder * @throws IllegalArgumentException if there is a handler collision */ public <G extends F> SubtypeEffectHandlerBuilder<F, E> addFunction( final Class<G> effectClass, final Function<G, E> function, Scheduler scheduler) { //noinspection ResultOfMethodCallIgnored checkNotNull(effectClass); //noinspection ResultOfMethodCallIgnored checkNotNull(function); return addTransformer(effectClass, Transformers.fromFunction(function, scheduler)); }
/** * Add an {@link Action0} for handling effects of a given type. The action will be invoked once * for every received effect object that extends the given class. * * <p>Adding handlers for two effect classes where one is a super-class of the other is * considered a collision and is not allowed. Registering the same class twice is also * considered a collision. * * @param effectClass the class to handle * @param action the action that should be invoked for the effect * @param <G> the effect class as a type parameter * @return this builder * @throws IllegalArgumentException if there is a handler collision */ public <G extends F> SubtypeEffectHandlerBuilder<F, E> addAction( final Class<G> effectClass, final Action0 action) { //noinspection ResultOfMethodCallIgnored checkNotNull(effectClass); //noinspection ResultOfMethodCallIgnored checkNotNull(action); return addTransformer(effectClass, Transformers.<G, E>fromAction(action)); }
/** * Add an {@link Action1} for handling effects of a given type. The action will be invoked once * for every received effect object that extends the given class. * * <p>Adding handlers for two effect classes where one is a super-class of the other is * considered a collision and is not allowed. Registering the same class twice is also * considered a collision. * * @param effectClass the class to handle * @param consumer the consumer that should be invoked for the effect * @param scheduler the scheduler that should be used to invoke the action * @param <G> the effect class as a type parameter * @return this builder * @throws IllegalArgumentException if there is a handler collision */ public <G extends F> SubtypeEffectHandlerBuilder<F, E> addConsumer( final Class<G> effectClass, final Action1<G> consumer, Scheduler scheduler) { //noinspection ResultOfMethodCallIgnored checkNotNull(effectClass); //noinspection ResultOfMethodCallIgnored checkNotNull(consumer); return addTransformer(effectClass, Transformers.<G, E>fromConsumer(consumer, scheduler)); }
@Test public void processingLongEffectsDoesNotBlockProcessingShorterEffects() { final List<String> effects = Arrays.asList("Hello", "Rx1"); PublishSubject<String> upstream = PublishSubject.create(); Function<String, Integer> sleepyFunction = s -> { try { Thread.sleep(duration(s)); } catch (InterruptedException ie) { } return s.length(); }; final List<Integer> results = new ArrayList<>(); upstream .compose(Transformers.fromFunction(sleepyFunction, Schedulers.io())) .subscribe(results::add); Observable.from(effects).subscribe(upstream); await().atMost(durationForEffects(effects)).until(() -> results.equals(expected(effects))); }
@Test public void effectPerformerRunsActionOnSchedulerWheneverEffectIsRequested() throws Exception { upstream.compose(Transformers.fromAction(action, scheduler)).subscribe(); upstream.onNext("First Time"); assertThat(action.getRunCount(), is(0)); scheduler.triggerActions(); assertThat(action.getRunCount(), is(1)); }
@Test public void effectPerformerInvokesConsumerOnSchedulerAndPassesTheRequestedEffect() throws Exception { upstream.compose(Transformers.fromConsumer(consumer, scheduler)).subscribe(); upstream.onNext("First Time"); assertThat(consumer.getCurrentValue(), is(equalTo(null))); scheduler.triggerActions(); assertThat(consumer.getCurrentValue(), is("First Time")); }
@Test public void effectPerformerInvokesFunctionWithReceivedEffectAndEmitsReturnedEvents() { PublishSubject<String> upstream = PublishSubject.create(); TestScheduler scheduler = new TestScheduler(); Function<String, Integer> function = s -> s.length(); AssertableSubscriber<Integer> observer = upstream.compose(Transformers.fromFunction(function, scheduler)).test(); upstream.onNext("Hello"); scheduler.triggerActions(); observer.assertValue(5); }
@Test public void effectPerformerRunsActionWheneverEffectIsRequested() throws Exception { upstream.compose(Transformers.fromAction(action)).subscribe(); upstream.onNext("First Time"); assertThat(action.getRunCount(), is(1)); upstream.onNext("One more!"); assertThat(action.getRunCount(), is(2)); }
@Test public void effectPerformerInvokesConsumerAndPassesTheRequestedEffect() throws Exception { upstream.compose(Transformers.fromConsumer(consumer)).subscribe(); upstream.onNext("First Time"); assertThat(consumer.getCurrentValue(), is("First Time")); upstream.onNext("Do it again!"); assertThat(consumer.getCurrentValue(), is("Do it again!")); }
@Test public void effectPerformerInvokesFunctionWithReceivedEffectAndErrorsForUnhandledExceptions() { PublishSubject<String> upstream = PublishSubject.create(); TestScheduler scheduler = new TestScheduler(); Function<String, Integer> function = s -> { throw new RuntimeException("Something bad happened"); }; AssertableSubscriber<Integer> observer = upstream.compose(Transformers.fromFunction(function, scheduler)).test(); upstream.onNext("Hello"); scheduler.triggerActions(); observer.assertError(RuntimeException.class); }
@Test public void consumerTransformerShouldPropagateCompletion() throws Exception { AssertableSubscriber<Object> subscriber = upstream.compose(Transformers.fromConsumer(consumer, scheduler)).test(); upstream.onNext("hi"); upstream.onCompleted(); scheduler.triggerActions(); subscriber.awaitTerminalEvent(1, TimeUnit.SECONDS); subscriber.assertCompleted(); }