Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Upcoming SlideShare
What to Upload to SlideShare
What to Upload to SlideShare
Loading in …3
×
1 of 24

Extending Retrofit for fun and profit

0

Share

Download to read offline

Internal talk given at Orion Health on extending Retrofit (an open source HTTP client) with your own Call Adapter for centralised error handling.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

Extending Retrofit for fun and profit

  1. 1. Extending Retrofit for fun and profit Matt Clarke - 4 July 2017, Orion Health Akl
  2. 2. Retrofit (v2.1.0) recap
  3. 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. 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. 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. 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. 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. 8. Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build(); GitHubService service = retrofit.create(GitHubService.class);
  9. 9. Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .addCallAdapterFactory( RxJavaCallAdapterFactory.create() ) .build(); GitHubService service = retrofit.create(GitHubService.class);
  10. 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. 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); }
  12. 12. Q: How does this magic work?
  13. 13. A: Call Adapters
  14. 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. 15. WTF is a Call Adapter? public interface CallAdapter<R, T> { T adapt(Call<R> call); Type responseType(); }
  16. 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. 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); // .. }
  18. 18. Concrete example - Engage Mobile
  19. 19. Problem: Network Error handling ● The RxJavaCallAdapter just forwards low-level network Exceptions via onError()
  20. 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. 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. 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. 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)
  24. 24. Live code demo

×