3. // Retrofit turns your HTTP API into a Java (or Kotlin)
interface
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
Define a light-weight interface that matches the REST endpoint you want to talk to
4. Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service =
retrofit.create(GitHubService.class);
Now you can use a Retrofit instance to generate a working version of your interface
to make requests with
5. Call<List<Repo>> repos = service.listRepos("octocat");
// synchronous/blocking request
Response<List<Repo>> response = repos.execute();
// do stuff with response...
To actually make a request, you just call the method you defined in your interface.
You’ll get back a Call object.
You can use this to make a sync request like this...
6. Call<List<Repo>> repos = service.listRepos("octocat");
// asynchronous request
repos.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call,
Response<List<Repo>> response) {
// do stuff with response...
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {
// uh oh!
}
});
Or you can use Call objects to make an asynchronous requests by calling enqueue
and passing a callback
7. Call<List<Repo>> repos = service.listRepos("octocat");
// asynchronous request
repos.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call,
Response<List<Repo>> response) {
// do stuff with response...
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {
// uh oh!
}
});
You might notice there are some similarities here with a library we’re all familiar with.
This Callback object looks a lot like an RxJava Observer or Subscriber.
Wouldn’t it be cool if the service returned an Observable instead of a Call? Then we
could go nuts with operators, subscribing on a different thread, etc.
8. Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
9. Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addCallAdapterFactory(
RxJavaCallAdapterFactory.create()
)
.build();
GitHubService service = retrofit.create(GitHubService.class);
10. // Retrofit turns your HTTP API into a Java (or Kotlin)
interface
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
11. // Retrofit turns your HTTP API into a Java (or Kotlin)
interface
public interface GitHubService {
@GET("users/{user}/repos")
Observable<List<Repo>> listRepos(@Path("user") String user);
}
14. WTF is a Call Adapter?
● Knows how to convert Call<R> to some other
type T
● E.g. the RxJavaCallAdapter can convert a
Call<Repo> into Observable<Repo>
15. WTF is a Call Adapter?
public interface CallAdapter<R, T> {
T adapt(Call<R> call);
Type responseType();
}
16. WTF is a Call Adapter?
● You also need to create a
CallAdapter.Factory to give to the
Retrofit.Builder
● Factories are given the chance to create
CallAdapters for a given return type in the
Service interface (e.g. Observable)
17. abstract class Factory {
/**
* Returns a call adapter for interface methods that
* return returnType, or null if it cannot be handled by
* this factory.
*/
public abstract @Nullable CallAdapter<?, ?> get(Type returnType,
Annotation[] annotations,
Retrofit retrofit);
// ..
}
19. Problem: Network Error handling
● The RxJavaCallAdapter just forwards low-level network
Exceptions via onError()
20. Problem: Network Error handling
val repoObservable = service.listRepos("octocat")
repoObservable.subscribe(object : Observer<List<Repo>> {
override fun onNext(repos: List<Repo>) {
// show repos in UI...
}
override fun onCompleted() {}
override fun onError(e: Throwable) {
when (e) {
is SocketTimeoutException -> showTimeoutError()
is ConnectException -> showNoConnectionToServerError()
is UnknownHostException -> showNoNetworkConnectionError()
is HttpException -> makeSenseOfHttpResponseCode(e)
}
}
})
You need to handle all of the
possible network errors
somewhere in order to do the
right thing in the UI.
Don’t want to have a copy of this
`when` statement everywhere
there’s a network call.
Also, these errors are low-level
and depend on the HTTP client
you’re using: if you change
HTTP libraries you don’t want all
of your UI code to break - they’re
separate concerns.
21. Network Error Handling
sealed class NetworkError(cause: Throwable?) :
Throwable(cause) {
class NetworkUnreachableError(cause: Throwable? = null) :
NetworkError(cause)
class ServerUnreachableError(cause: Throwable? = null) :
NetworkError(cause)
class GenericNetworkError(cause: Throwable? = null) :
NetworkError(cause)
} Solution: abstract away low-level HTTP client errors by defining your own
higher-level network error types
22. Network Error Handling
fun map(input: Throwable): Throwable {
if (!networkAccessAvailable())
return NetworkError.NetworkUnreachableError(input)
return when (input) {
is SocketTimeoutException ->
NetworkError.ServerUnreachableError(cause = input)
is ConnectException ->
NetworkError.ServerUnreachableError(cause = input)
is UnknownHostException ->
NetworkError.NetworkUnreachableError(cause = input)
is HttpException ->
mapHttpException(input)
else -> input
}
}
...Then you can extract out a common network error mapper,
and apply this to the reactive stream with the
onErrorResumeNext operator
23. Network Error Handling
repoObservable
.onErrorResumeNext { e ->
Observable.error(mapNetworkError(e))
}
.subscribe(object : Observer<List<Repo>> {
// ..
})
The problem with this is you still need to remember to apply this
mapper to all reactive streams that contain a network request.
Having to remember = a red flag. Better to structure the system
such that it does the right thing by default (remembers for you)