This is a living spec for what will be v2 of Retrofit.
Goals
- Broaden the scope of how you can process successful responses and handle the results of various exceptions throughout the stack. Currently there's a mix of exceptions, wrapped types, and callbacks depending on the usage of synchronous or asynchronous API declarations.
- Unify API declaration so that both synchronous and asynchronous consumption of a resource does not change its declaration.
- Introduce better request and response interceptors which allow for both tweaks to existing requests or full on replacement (e.g., for signing purposes).
- Allow simple request retry and cancel support without the need to talk to the
RestAdapter
or generated interface instance.
- Completely decouple interface declaration parsing from request invocation and processing and expose the shim layer between the two as a extension-based system for customization. This will be used to (hopefully) facilitate RxJava integration as an extensions rather than being baked into the core of the library itself
Changes
Annotation Processors
An annotation processor will be embedded inside the core artifact for compile-time verification of REST API interface declarations.
A second annotation processor will be provided as a separate artifact for full code generation of a class implementation of each API interface.
The RestAdapter
will always do a read-through cached lookup for the generated classes since it has no knowledge of whether the code-gen processor was used and we don't want to place the burden on the caller either.
Request Object
All interface declarations will be required to return an object through which all interaction will occur. The behavior of this object will be similar to a Future
and will be generic typed (T
) for the success response type (ref: #231).
@GET("/foo")
Call<Foo> getFoo();
Callers can synchronously call .execute()
which will return type T
. Exceptions will be thrown for any error, network error, unsuccessful response, or unsuccessful deserialization of the response body. While these exceptions will likely extend from the same supertype, it's unclear as to whether that supertype should be checked or unchecked.
interface Api {
@GET("/{user}/tweets")
Call<List<Tweet>> listTweets(@Path("user") String username);
}
List<Tweet> tweets = api.listTweets("JakeWharton").execute();
Callers can also supply callbacks to this object for asynchronous notification of the response. The traditional Callback<T>
of the current version of Retrofit will be available. One change will be that the error object passed to failure
will not be the same exception as would be thrown in synchronous execution but rather something a bit more transparent to the underlying cause.
interface Api {
@GET("/{user}/tweets")
Call<List<Tweet>> listTweets(@Path("user") String username);
}
Call<List<Tweet>> c = api.listTweets("JakeWharton");
c.execute(new Callback<List<Tweet>>() {
@Override public void success(List<Tweet> response) { }
// ...
}
TODO describe error handling
public abstract class ResponseCallback<T> {
public abstract void success(T response);
public void networkError(IOException e) {
error(ErrorType.NETWORK, -1, e);
}
public void processingError(Exception e) {
error(ErrorType.PROCESSING, -1, e);
}
public void httpError(int status, ...) {
error(ErrorType.HTTP, status, e);
}
public void error(ErrorType errorType, int status, Exception e) {
throw new RuntimeException(.., e);
}
public enum ErrorType { NETWORK, PROCESSING, HTTP }
}
The call object is also an obvious place for handling the retry and cancelation of requests. Both are a simple, no-args method on the object which can only be called at appropriate times.
cancel()
is a no-op after the response has been received. In all other cases the method will set any callbacks to null
(thus freeing strong references to the enclosing class if declared anonymously) and render the request object dead. All future interactions with the request object will throw an exception. If the request is waiting in the executor its Future
will be cancelled so that it is never invoked.
retry()
will re-submit the request onto the backing executor without passing through any of the mutating pipeline described above. Retrying a request is only available after a network error or 5XX response. Attempting to retry a request that is currently in flight, after a non-5XX response, after an unexpected error, or after calling cancel()
will throw an exception.
Extension System
In order to facilitate libraries which offer semantics on both synchronous and asynchronous operations without requiring a wrapper, we will introduce a system which we will tentatively refer to as extensions. An extension instance will be registered with the RestAdapter
and associate itself as handling an explicit interface method return type.
By default, Retrofit will install its own extension which handles the aforementioned Call
type for both synchronous and asynchronous request execution.
The current (albeit experimentally denoted) RxJava integration will also be provided as an opt-in extension. This extension will register itself as handling the Observable
type which will allow the declaration of interfaces which return this type.
interface FooService {
@GET("/foos")
Observable<Foo> getFoo();
}
And more...
TODO!