Skip to content

Commit

Permalink
Final touchups for 0.5.0 release (#144)
Browse files Browse the repository at this point in the history
* Opportunistic grade updates

Puts the buildscript/repo config back in the top level (I think they were accidentally moved)

Switches to file base api for checkstyle destination, previous was deprecated

* Various README updates

This gives the README some much-needed love.
* Update for the new as() API
* Consolidate duplicate plugin sections (wat)
* Document public AutoDisposing observers and their delegateObserver() methods
* Some various other fine tunings

* Remove experimental from delegateObserver()

This is not going to be changed in the future, as it's useful for subscribeWith scenarios too and we don't need to unnecessarily tie it down
  • Loading branch information
ZacSweers authored Dec 4, 2017
1 parent deab8fb commit 87330fb
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 65 deletions.
83 changes: 49 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ the relevant factory call + method for that type as a converter. In everyday use
```java
myObservable
.doStuff()
.to(AutoDispose.with(this).forObservable()) // The magic
.as(autoDisposable(this)) // The magic
.subscribe(s -> ...);
```

Expand All @@ -29,25 +29,22 @@ scope - this helps prevent many classes of errors when an observable emits and i
taken in the subscription are no longer valid. For instance, if a network request comes back after a
UI has already been torn down, the UI can't be updated - this pattern prevents this type of bug.

### `with()` + `<type>()`
### `autoDisposable()`

The main entry point is via static factory `with()` methods in the `AutoDispose` class. There are
three overloads: `Maybe`, `ScopeProvider`, and `LifecycleScopeProvider`. They return a
`ScopeHandler` object that's just intended to be an intermediary to route to the desired type
function (this is for better autocomplete in IDEs for the generics).
The main entry point is via static factory `autoDisposable()` methods in the `AutoDispose` class.
There are three overloads: `Maybe`, `ScopeProvider`, and `LifecycleScopeProvider`. They return an
`AutoDisposeConverter` object that implements all the RxJava `Converter` interfaces for use with
the `as()` operator in RxJava types.

For the relevant `type` methods, there is one per RxJava type (`forObservable()`, `forSingle()`, etc).
These return implementations of a converter `Function`, intended for use with the `to()` operator in RxJava
types.

These work in tandem to create the regular AutoDispose flow. `AutoDispose.with(scope).forObservable()`.

#### Maybe
#### Maybe (as a scope)

The `Maybe` semantic is modeled after the `takeUntil()` operator, which accepts an `Observable`
whose first emission is used as a notification to signal completion. This is is logically the
behavior of a `Maybe`, so we choose to make that explicit. All scopes eventually resolve to a single
`Maybe` that emits the end-of-scope notification.
behavior of a `Single`, so we choose to make that explicit. Scope providers may want to dynamically
indicate that a scope is "unbound" though, so we use a `Maybe` to indicate this via its completion.
All scopes in AutoDispose eventually resolve to a `Maybe` that emits the end-of-scope notification
in `onSuccess` or signals that execution is unbound via `onComplete`. `onError` will pass through to
the underlying subscription.

#### Providers

Expand Down Expand Up @@ -95,28 +92,17 @@ public interface ScopeProvider {
This is particularly useful for objects with simple scopes ("stop when I stop") or very custom state
that requires custom handling.

#### Plugins

When a lifecycle has not started or has already ended, `AutoDispose` will send an error event with an
`OutsideLifecycleException` to downstream consumers. If you want to customize this behavior, you can use
`AutoDisposePlugins` to intercept these exceptions and rethrow something else or nothing at all.

### Behavior

The created observer encapsulates the parameters of `around` to create a disposable, auto-disposing
observer that acts as a lambda observer (pass-through) unless the underlying scope `Maybe` emits.
Both scope end and upstream termination result in immediate disposable of both the underlying scope
subscription and upstream disposable.

### Support/Extensions

`Flowable`, `Observable`, `Maybe`, `Single`, and `Completable` are all supported. Implementation is solely
based on their `Observer` types, so conceivably any type that uses those for subscription should work.

#### AutoDisposePlugins

Modeled after RxJava's plugins, this allows you to customize the behavior of AutoDispose.

##### OutsideLifecycleHandler

When a lifecycle has not started or has already ended, `AutoDispose` will send an error event with an
`OutsideLifecycleException` to downstream consumers. If you want to customize this behavior, you can use
`AutoDisposePlugins#setOutsideLifecycleHandler` to intercept these exceptions and rethrow something
else or nothing at all.

Example
```java
AutoDisposePlugins.setOutsideLifecycleHandler(t -> {
Expand All @@ -126,6 +112,36 @@ AutoDisposePlugins.setOutsideLifecycleHandler(t -> {

A good use case of this is, say, just silently disposing/logging observers outside of lifecycle exceptions in production but crashing on debug.

##### FillInOutsideLifecycleExceptionStacktraces

If you have your own handling of exceptions in lifecycle boundary events, you can optionally set
`AutoDisposePlugins#setFillInOutsideLifecycleExceptionStacktraces` to `false`. This will result in
AutoDispose `not` filling in stacktraces for exceptions, for a potential minor performance boost.

### Behavior

Under the hood, AutoDispose decorates RxJava's real observer with a custom *AutoDisposing* observer.
This custom observer leverages the scope to create a disposable, auto-disposing observer that acts
as a lambda observer (pass-through) unless the underlying scope `Maybe` emits `onSuccess`. Both
scope emission and upstream termination result in immediate disposable of both the underlying scope
subscription and upstream disposable.

In the event that the scope `Maybe` emits `onComplete`, the execution is unbound (as if autodispose
was never enabled on the observation).

These custom `AutoDisposing` observers are considered public read-only API, and can be found under the
`observers` package. They also support retrieval of the underlying observer via `delegateObserver()`
methods. Read-only API means that the public signatures will follow semantic versioning, but we may
add new methods in the future (which would break compilation if you make custom implementations!).

To read this information, you can use RxJava's `onSubscribe` hooks in `RxJavaPlugins` to watch for
instances of these observers.

### Support/Extensions

`Flowable`, `Observable`, `Maybe`, `Single`, and `Completable` are all supported. Implementation is solely
based on their `Observer` types, so conceivably any type that uses those for subscription should work.

#### Extensions

There are also a number of extension artifacts available, detailed below.
Expand Down Expand Up @@ -187,7 +203,6 @@ Another caveat we often ran into (and later aggressively linted against) was tha
ordering implications, and needed to be as close to the `subscribe()` call as possible to properly wrap upstream.
If binding to views, there were also threading requirements on the observable chain in order to work properly.


At the end of the day, we wanted true disposal/unsubscription-based behavior, but with RxLifecycle-esque
semantics around scope resolution. RxJava 2's `Observer` interfaces provide the perfect mechanism for
this via their `onSubscribe()` callbacks. The result is de-risked `Single`/`Completable` usage, no ordering
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.uber.autodispose.observers;

import io.reactivex.CompletableObserver;
import io.reactivex.annotations.Experimental;
import io.reactivex.disposables.Disposable;

/**
Expand All @@ -28,8 +27,7 @@ public interface AutoDisposingCompletableObserver extends CompletableObserver, D

/**
* @return The delegate {@link CompletableObserver} that is used under the hood for introspection
* purposes. This will be updated once LambdaIntrospection is out of @Experimental in RxJava.
* purposes.
*/
@Experimental
CompletableObserver delegateObserver();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.uber.autodispose.observers;

import io.reactivex.MaybeObserver;
import io.reactivex.annotations.Experimental;
import io.reactivex.disposables.Disposable;

/**
Expand All @@ -27,9 +26,8 @@
public interface AutoDisposingMaybeObserver<T> extends MaybeObserver<T>, Disposable {

/**
* @return The delegate {@link MayberObserver} that is used under the hood for introspection
* purposes. This will be updated once LambdaIntrospection is out of @Experimental in RxJava.
* @return The delegate {@link MaybeObserver} that is used under the hood for introspection
* purposes.
*/
@Experimental
MaybeObserver<? super T> delegateObserver();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.uber.autodispose.observers;

import io.reactivex.Observer;
import io.reactivex.annotations.Experimental;
import io.reactivex.disposables.Disposable;

/**
Expand All @@ -28,8 +27,6 @@ public interface AutoDisposingObserver<T> extends Observer<T>, Disposable {

/**
* @return The delegate {@link Observer} that is used under the hood for introspection purposes.
* This will be updated once LambdaIntrospection is out of @Experimental in RxJava.
*/
@Experimental
Observer<? super T> delegateObserver();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.uber.autodispose.observers;

import io.reactivex.SingleObserver;
import io.reactivex.annotations.Experimental;
import io.reactivex.disposables.Disposable;

/**
Expand All @@ -28,8 +27,7 @@ public interface AutoDisposingSingleObserver<T> extends SingleObserver<T>, Dispo

/**
* @return The delegate {@link SingleObserver} that is used under the hood for introspection
* purposes. This will be updated once LambdaIntrospection is out of @Experimental in RxJava.
* purposes.
*/
@Experimental
SingleObserver<? super T> delegateObserver();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.uber.autodispose.observers;

import io.reactivex.FlowableSubscriber;
import io.reactivex.annotations.Experimental;
import io.reactivex.disposables.Disposable;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
Expand All @@ -31,8 +30,7 @@ public interface AutoDisposingSubscriber<T>

/**
* @return The delegate {@link Subscriber} that is used under the hood for introspection
* purposes. This will be updated once LambdaIntrospection is out of @Experimental in RxJava.
* purposes.
*/
@Experimental
Subscriber<? super T> delegateSubscriber();
}
13 changes: 13 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
* limitations under the License.
*/

subprojects {
buildscript {
repositories {
jcenter()
}
}

repositories {
jcenter()
google()
}
}

task wrapper(type: Wrapper) {
gradleVersion = '4.3.1'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
Expand Down
17 changes: 2 additions & 15 deletions gradle/checkstyle.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,6 @@
*/

subprojects {
buildscript {
repositories {
jcenter()
}
}

repositories {
jcenter()
maven {
url 'https://maven.google.com'
}
}

apply plugin: 'checkstyle'

afterEvaluate {
Expand All @@ -43,7 +30,7 @@ subprojects {
exclude '**/R.java'
exclude '**/BuildConfig.java'
reports {
xml.destination "$project.buildDir/reports/checkstyle/main.xml"
xml.destination new File("$project.buildDir/reports/checkstyle/main.xml")
}
classpath = files()
configFile = rootProject.file('checkstyle.xml')
Expand All @@ -58,7 +45,7 @@ subprojects {
exclude '**/R.java'
exclude '**/BuildConfig.java'
reports {
xml.destination "$project.buildDir/reports/checkstyle/test.xml"
xml.destination new File("$project.buildDir/reports/checkstyle/test.xml")
}
classpath = files()
configFile = rootProject.file('checkstyle.xml')
Expand Down

0 comments on commit 87330fb

Please sign in to comment.