Releases: ReactiveX/RxJava
0.17.0
Version 0.17.0 contains some significant signature changes that allow us to significantly improve handling of synchronous Observables and simplify Schedulers. Many of the changes have backwards compatible deprecated methods to ease the migration while some are breaking.
The new signatures related to Observable
in this release are:
// A new create method takes `OnSubscribe` instead of `OnSubscribeFunc`
public final static <T> Observable<T> create(OnSubscribe<T> f)
// The new OnSubscribe type accepts a Subscriber instead of Observer and does not return a Subscription
public static interface OnSubscribe<T> extends Action1<Subscriber<? super T>>
// Subscriber is an Observer + Subscription
public abstract class Subscriber<T> implements Observer<T>, Subscription
// The main `subscribe` behavior receives a Subscriber instead of Observer
public final Subscription subscribe(Subscriber<? super T> subscriber)
// Subscribing with an Observer however is still appropriate
// and the Observer is automatically converted into a Subscriber
public final Subscription subscribe(Observer<? super T> observer)
// A new 'lift' function allows composing Operator implementations together
public <R> Observable<R> lift(final Operator<? extends R, ? super T> lift)
// The `Operator` used with `lift`
public interface Operator<R, T> extends Func1<Subscriber<? super R>, Subscriber<? super T>>
Also changed is the Scheduler
interface which is much simpler:
public abstract class Scheduler {
public Subscription schedule(Action1<Scheduler.Inner> action);
public Subscription schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
public Subscription schedulePeriodically(Action1<Scheduler.Inner> action, long initialDelay, long period, TimeUnit unit);
public final Subscription scheduleRecursive(final Action1<Recurse> action)
public long now();
public int degreeOfParallelism();
public static class Inner implements Subscription {
public abstract void schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
public abstract void schedule(Action1<Scheduler.Inner> action);
public long now();
}
public static final class Recurse {
public final void schedule();
public final void schedule(long delay, TimeUnit unit);
}
}
This release applies many lessons learned over the past year and seeks to streamline the API before we hit 1.0.
As shown in the code above the changes fall into 2 major sections:
1) Lift/Operator/OnSubscribe/Subscriber
Changes that allow unsubscribing from synchronous Observables without needing to add concurrency.
2) Schedulers
Simplification of the Scheduler
interface and make clearer the concept of "outer" and "inner" Schedulers for recursion.
Lift/Operator/OnSubscribe/Subscriber
New types Subscriber
and OnSubscribe
along with the new lift
function have been added. The reasons and benefits are as follows:
1) Synchronous Unsubscribe
RxJava versions up until 0.16.x are unable to unsubscribe from a synchronous Observable such as this:
Observable<Integer> oi = Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Observer<? super Integer> Observer) {
for (int i = 1; i < 1000000; i++) {
subscriber.onNext(i);
}
subscriber.onCompleted();
}
});
Subscribing to this Observable
will always emit all 1,000,000 values even if unsubscribed such as via oi.take(10)
.
Version 0.17.0 fixes this issue by injecting the Subscription
into the OnSubscribe
function to allow code like this:
Observable<Integer> oi = Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> subscriber) {
// we now receive a Subscriber instead of Observer
for (int i = 1; i < 1000000; i++) {
// the OnSubscribe can now check for isUnsubscribed
if (subscriber.isUnsubscribed()) {
return;
}
subscriber.onNext(i);
}
subscriber.onCompleted();
}
});
Subscribing to this will now correctly only emit 10 onNext
and unsubscribe:
// subscribe with an Observer
oi.take(10).subscribe(new Observer<Integer>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer t) {
println("Received: " + t);
}
})
Or the new Subscriber
type can be used and the Subscriber
itself can unsubscribe
:
// or subscribe with a Subscriber which supports unsubscribe
oi.subscribe(new Subscriber<Integer>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer t) {
println("Received: " + t);
if(t >= 10) {
// a Subscriber can unsubscribe
this.unsubscribe();
}
}
})
2) Custom Operator Chaining
Because Java doesn't support extension methods, the only approach to applying custom operators without getting them added to rx.Observable
is using static methods. This has meant code like this:
MyCustomerOperators.operate(observable.map(...).filter(...).take(5)).map(...).subscribe()
In reality we want:
observable.map(...).filter(...).take(5).myCustomOperator().map(...).subscribe()
Using the newly added lift
we can get quite close to this:
observable.map(...).filter(...).take(5).lift(MyCustomOperator.operate()).map(...).subscribe()
Here is how the proposed lift
method looks if all operators were applied with it:
Observable<String> os = OBSERVABLE_OF_INTEGERS.lift(TAKE_5).lift(MAP_INTEGER_TO_STRING);
Along with the lift
function comes a new Operator
signature:
public interface Operator<R, T> extends Func1<Subscriber<? super R>, Subscriber<? super T>>
All operator implementations in the rx.operators
package will over time be migrated to this new signature.
NOTE: Operators that have not yet been migrated do not work with synchronous unsubscribe.
3) Simpler Operator Implementations
The lift
operator injects the necessary Observer
and Subscription
instances (via the new Subscriber
type) and eliminates (for most use cases) the need for manual subscription management. Because the Subscription
is available in-scope there are no awkward coding patterns needed for creating a Subscription
, closing over it and returning and taking into account synchronous vs asynchronous.
For example, the body of fromIterable
is simply:
public void call(Subscriber<? super T> o) {
for (T i : is) {
if (o.isUnsubscribed()) {
return;
}
o.onNext(i);
}
o.onCompleted();
}
The take
operator is:
public Subscriber<? super T> call(final Subscriber<? super T> child) {
final CompositeSubscription parent = new CompositeSubscription();
if (limit == 0) {
child.onCompleted();
parent.unsubscribe();
}
child.add(parent);
return new Subscriber<T>(parent) {
int count = 0;
boolean completed = false;
@Override
public void onCompleted() {
if (!completed) {
child.onCompleted();
}
}
@Override
public void onError(Throwable e) {
if (!completed) {
child.onError(e);
}
}
@Override
public void onNext(T i) {
if (!isUnsubscribed()) {
child.onNext(i);
if (++count >= limit) {
completed = true;
child.onCompleted();
unsubscribe();
}
}
}
};
}
4) Recursion/Loop Performance with Unsubscribe
The fromIterable
use case is 20x faster when implemented as a loop instead of recursive scheduler (see a18b8c1).
Several places we can remove recursive scheduling used originally for unsubscribe support and use a loop instead.
Schedulers
Schedulers were greatly simplified to a design based around Action1<Inner>
.
public abstract class Scheduler {
public Subscription schedule(Action1<Scheduler.Inner> action);
public Subscription schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
public Subscription schedulePeriodically(Action1<Scheduler.Inner> action, long initialDelay, long period, TimeUnit unit);
public final Subscription scheduleRecursive(final Action1<Recurse> action)
public long now();
public int degreeOfParallelism();
public static class Inner implements Subscription {
public abstract void schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
public abstract void schedule(Action1<Scheduler.Inner> action);
public long now();
}
public static final class Recurse {
public final void schedule();
public final void schedule(long delay, TimeUnit unit);
}
}
This design change originated from three findings:
- It was very easy to cause memory leaks or inadvertent parallel execution since the distinction between outer and inner scheduling was not obvious.
To solve this the new design explicitly has the outer Scheduler
and then Scheduler.Inner
for recursion.
- The passing of state is not useful since scheduling over network boundaries with this model does not work.
In this new design all state passing signatures have been removed. This was determined while implem...
0.17.0 Release Candidate 7
Pre-release of 0.17.0 for testing. See details at #802.
0.17.0 Release Candidate 6
Pre-release of 0.17.0 for testing. See details at #802.
Compared with RC5 this has changes to:
- Clojure bindings
- TestSubscriber
0.17.0 Release Candidate 5
Pre-release of 0.17.0 for testing. See details at #802.
Compared with RC4 this has changes to:
- error handling of operators via
lift
- debug module
0.17.0 Release Candidate 4
Pre-release of 0.17.0 for testing. See details at #802.
Compared with RC3 this has changes to:
- doOnTerminate
- zip bug fix
- execution hook cleanup
0.17.0 Release Candidate 3
Pre-release of 0.17.0 for testing. See details at #802.
Compared with RC2 this has changes to:
- a new RxJavaDefaultSchedulers plugin
- bug fixes
0.17.0 Release Candidate 2
Pre-release of 0.17.0 for testing. See details at #802.
Compared with RC1 this has changes to:
observeOn
subscribeOn
- new
unsubscribeOn
operator - Android and Swing component Subscriptions
- movement of
rx.util
packages - error handling
0.17.0 Release Candidate 1
Pre-release of 0.17.0 for testing. See details at #802.
In particular please test:
- observeOn
- subscribeOn
- groupBy
- zip
- Scheduler
0.16.1
- Pull 730 Improve Error Handling and Stacktraces When Unsubscribe Fails
- Pull 720 Added
Observable.timeout
wrappers to scala adapter - Pull 731 Fix non-deterministic unit test
- Pull 742 Build with Gradle 1.10
- Pull 718 Merge overloads
- Pull 733 Buffer with Observable boundary
- Pull 734 Delay with subscription and item delaying observables
- Pull 735 Window with Observable boundary
- Pull 736 MergeMap with Iterable and resultSelector overloads
- Pull 738 Publish and PublishLast overloads
- Pull 739 Debounce with selector
- Pull 740 Timeout with selector overloads
- Pull 745 Fixed
switch
bug - Pull 741 Zip with iterable, removed old aggregator version and updated tests
- Pull 749 Separated Android test code from source
- Pull 732 Ported groupByUntil function to scala-adapter
Artifacts: Maven Central
0.16.0
This is a significant release with the following changes:
- Refactor of Subjects and Subscriptions to non-blocking implementations
- Many bug fixes, new operators and behavior changes to match Rx.Net.
- Deprecation of some operators due to renaming or eliminating duplicates
- The
rx.concurrency
package has been renamed torx.schedulers
. Existing classes still remain inrx.concurrency
but are deprecated. Use ofrx.concurrency
should be migrated torx.schedulers
as these deprecated classes will be removed in a future release. - Breaking changes to Scala bindings. See Release Notes for details.
- New modules: rxjava-string, rxjava-async-util and rxjava-computation-expressions for operators deemed not applicable to the core library.
- Pull 516 rxjava-string module with StringObservable
- Pull 533 Operator: ToAsync
- Pull 535 Fix compilation errors due to referencing the Android support library directly
- Pull 545 Fixed Zip issue with infinite streams
- Pull 539 Zipping a finite and an infinite Observable
- Pull 541 Operator: SkipUntil
- Pull 537 Add scala adapters for doOnEach operator
- Pull 560 Add type variances for doOnEach actions
- Pull 562 Scala Adaptor Improvements
- Pull 563 Operator: GroupByUntil
- Pull 561 Revised Approach to Creating Observables in Scala
- Pull 565 Operator: GroupJoin v2
- Pull 567 Operator: Timestamp with Scheduler
- Pull 568 Use lock free strategy for several Subscription implementations
- Pull 571 Operator: Sample with Observable v2
- Pull 572 Multiple Subscriptions to ObserveOn
- Pull 573 Removed Opening and Closing historical artifacts
- Pull 575 Operator: SequenceEqual reimplementation
- Pull 587 Operator: LongCount
- Pull 586 Fix Concat to allow multiple observers
- Pull 598 New Scala Bindings
- Pull 596 Fix for buffer not stopping when unsubscribed
- Pull 576 Operators: Timer and Delay
- Pull 593 Lock-free subscriptions
- Pull 599 Refactor rx.concurrency to rx.schedulers
- Pull 600 BugFix: Replay Subject
- Pull 594 Operator: Start
- Pull 604 StringObservable.join
- Pull 609 Operation: Timer
- Pull 612 Operation: Replay (overloads)
- Pull 628 BugFix: MergeDelayError Synchronization
- Pull 602 BugFix: ObserveOn Subscription leak
- Pull 631 Make NewThreadScheduler create Daemon threads
- Pull 651 Subjects Refactor - Non-Blocking, Common Abstraction, Performance
- Pull 661 Subscriptions Rewrite
- Pull 520 BugFix: blocking/non-blocking
first
- Pull 621 Scala: SerialSubscription & From
- Pull 626 BO.Latest, fixed: BO.next, BO.mostRecent, BO.toIterable
- Pull 633 BugFix: null in toList operator
- Pull 635 Conditional Operators
- Pull 638 Operations: DelaySubscription, TakeLast w/ time, TakeLastBuffer
- Pull 659 Missing fixes from the subject rewrite
- Pull 688 Fix SafeObserver handling of onComplete errors
- Pull 690 Fixed Scala bindings
- Pull 693 Kotlin M6.2
- Pull 689 Removed ObserverBase
- Pull 664 Operation: AsObservable
- Pull 697 Operations: Skip, SkipLast, Take with time
- Pull 698 Operations: Average, Sum
- Pull 699 Operation: Repeat
- Pull 701 Operation: Collect
- Pull 707 Module: rxjava-async-util
- Pull 708 BugFix: combineLatest
- Pull 712 Fix Scheduler Memory Leaks
- Pull 714 Module: rxjava-computation-expressions
- Pull 715 Add missing type hint to clojure example
- Pull 717 Scala: Added ConnectableObservable
- Pull 723 Deprecate multiple arity ‘from’
- Pull 724 Revert use of CurrentThreadScheduler for Observable.from
- Pull 725 Simpler computation/io naming for Schedulers
- Pull 727 ImmediateScheduler optimization for toObservableIterable
Artifacts: Maven Central