/** * Creates an {@link ObservableTransformer} that will flatten the provided {@link Action} 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 effect transformer. * * @param doEffect the {@link Action} 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 an {@link ObservableTransformer} that can be used with a {@link * SubtypeEffectHandlerBuilder}. */ static <F, E> ObservableTransformer<F, E> fromAction(final Action doEffect) { return fromAction(doEffect, null); }
/** * Creates an {@link ObservableTransformer} that will flatten the provided {@link Consumer} 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 and passing it the requested effect * object. * * @param doEffect the {@link Consumer} 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 an {@link ObservableTransformer} that can be used with a {@link * SubtypeEffectHandlerBuilder}. */ static <F, E> ObservableTransformer<F, E> fromConsumer(final Consumer<F> doEffect) { return fromConsumer(doEffect, null); }
/** * Creates an {@link ObservableTransformer} that will flatten the provided {@link Function} 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 Function} 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 ObservableTransformer} that can be used with a {@link * SubtypeEffectHandlerBuilder}. */ static <F, E> ObservableTransformer<F, E> fromFunction(final Function<F, E> function) { return fromFunction(function, null); } }
/** * Add a {@link Function} 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 Action} 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 Action action) { //noinspection ResultOfMethodCallIgnored checkNotNull(effectClass); //noinspection ResultOfMethodCallIgnored checkNotNull(action); return addTransformer(effectClass, Transformers.<G, E>fromAction(action)); }
/** * Add an {@link Consumer} for handling effects of a given type. The consumer 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 consumer * @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 Consumer<G> consumer, Scheduler scheduler) { //noinspection ResultOfMethodCallIgnored checkNotNull(effectClass); //noinspection ResultOfMethodCallIgnored checkNotNull(consumer); return addTransformer(effectClass, Transformers.<G, E>fromConsumer(consumer, scheduler)); }
/** * Add a {@link Function} 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 Action} 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 Action action, Scheduler scheduler) { //noinspection ResultOfMethodCallIgnored checkNotNull(effectClass); //noinspection ResultOfMethodCallIgnored checkNotNull(action); return addTransformer(effectClass, Transformers.<G, E>fromAction(action, scheduler)); }
/** * Add an {@link Consumer} for handling effects of a given type. The consumer 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 Consumer<G> consumer) { //noinspection ResultOfMethodCallIgnored checkNotNull(effectClass); //noinspection ResultOfMethodCallIgnored checkNotNull(consumer); return addTransformer(effectClass, Transformers.<G, E>fromConsumer(consumer)); }
@Test public void processingLongEffectsDoesNotBlockProcessingShorterEffects() { final List<String> effects = Arrays.asList("Hello", "Rx2"); 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.fromIterable(effects).subscribe(upstream); await().atMost(durationForEffects(effects)).until(() -> results.equals(expected(effects))); }
@Test public void effectPerformerRunsActionWheneverEffectIsRequested() throws Exception { PublishSubject<String> upstream = PublishSubject.create(); TestAction action = new TestAction(); 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 { PublishSubject<String> upstream = PublishSubject.create(); TestConsumer<String> consumer = new TestConsumer<>(); 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 effectPerformerInvokesFunctionWithReceivedEffectAndEmitsReturnedEvents() { PublishSubject<String> upstream = PublishSubject.create(); TestScheduler scheduler = new TestScheduler(); Function<String, Integer> function = s -> s.length(); TestObserver<Integer> observer = upstream.compose(Transformers.fromFunction(function, scheduler)).test(); upstream.onNext("Hello"); scheduler.triggerActions(); observer.assertValue(5); }
@Test public void effectPerformerRunsActionOnSchedulerWheneverEffectIsRequested() throws Exception { PublishSubject<String> upstream = PublishSubject.create(); TestAction action = new TestAction(); TestScheduler scheduler = new TestScheduler(); 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 { PublishSubject<String> upstream = PublishSubject.create(); TestConsumer<String> consumer = new TestConsumer<>(); TestScheduler scheduler = new TestScheduler(); 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 effectPerformerInvokesFunctionWithReceivedEffectAndErrorsForUnhandledExceptions() { PublishSubject<String> upstream = PublishSubject.create(); TestScheduler scheduler = new TestScheduler(); Function<String, Integer> function = s -> { throw new RuntimeException("Something bad happened"); }; TestObserver<Integer> observer = upstream.compose(Transformers.fromFunction(function, scheduler)).test(); upstream.onNext("Hello"); scheduler.triggerActions(); observer.assertError(RuntimeException.class); }