Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Simple HTTP with Retrofit 2 (Droidcon NYC 2015)

Simple HTTP with Retrofit 2 (Droidcon NYC 2015)

Retrofit has been simplifying HTTP calls for years and this new version is no different. In addition to fixing some long-standing annoyances, there are a handful of new features which make it more powerful than ever.

This talk will focus on how the new APIs in Retrofit aid in making HTTP calls as simple as possible for your app. The integration with OkHttp and Okio APIs will be covered to ensure a full understanding of the HTTP stack. Common use-cases and design patterns through more advanced functionality will end the talk.

No prior Retrofit, OkHttp, or Okio exposure needed. The content may quickly cover or skip some introductory aspects in order to allow focus on more actionable and useful content.

Video: https://youtu.be/KIAoQbAu3eA

Jake Wharton

August 27, 2015
Tweet

More Decks by Jake Wharton

Other Decks in Programming

Transcript

  1. Retrofit Two
    Jake Wharton

    View full-size slide

  2. Retrofit 2 will be out
    by the end of this year
    " "
    — Naïve Man

    View full-size slide

  3. Retrofit 2 will be out
    by the end of this year
    " "
    — Jake Wharton

    View full-size slide

  4. Retrofit 2 will be out
    by the end of this year
    " "
    Droidcon NYC 2014
    — Jake Wharton

    View full-size slide

  5. Retrofit 2 will be out
    by the end of this year
    " "
    Droidcon NYC 2015
    — Jake Wharton

    View full-size slide

  6. Retrofit 1
    • Made open source on October 13th, 2010

    View full-size slide

  7. Retrofit 1
    • Made open source on October 13th, 2010
    • Originally a Bob Lee joint

    View full-size slide

  8. Retrofit 1
    • Made open source on October 13th, 2010
    • Originally a Bob Lee joint
    • Took over / stole stewardship mid-2012

    View full-size slide

  9. Retrofit 1
    • Made open source on October 13th, 2010
    • Originally a Bob Lee joint
    • Took over / stole stewardship mid-2012
    • Released 1.0 on May 13th, 2013

    View full-size slide

  10. Retrofit 1
    • Made open source on October 13th, 2010
    • Originally a Bob Lee joint
    • Took over / stole stewardship mid-2012
    • Released 1.0 on May 13th, 2013
    • 18 releases post-1.0 with tons of features / fixes

    View full-size slide

  11. The Good
    • Interface service declarations
    • Method and parameter annotations customize request

    View full-size slide

  12. Interface and annotations
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    List repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  13. The Good
    • Interface service declarations
    • Method and parameter annotations customize request
    • Pluggable HTTP client and serialization

    View full-size slide

  14. Pluggable client and serialization
    builder.setClient(new UrlConnectionClient());
    builder.setClient(new ApacheClient());
    builder.setClient(new OkClient());

    View full-size slide

  15. Pluggable client and serialization
    builder.setClient(new UrlConnectionClient());
    builder.setClient(new ApacheClient());
    builder.setClient(new OkClient());
    builder.setClient(new CustomClient());

    View full-size slide

  16. Pluggable client and serialization
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    List repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  17. Pluggable client and serialization
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    List repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    builder.setConverter(new GsonConverter());
    builder.setConverter(new JacksonConverter());

    View full-size slide

  18. Pluggable client and serialization
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    ContributorResponse repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  19. Pluggable client and serialization
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    ContributorResponse repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    builder.setConverter(new ProtoConverter());
    builder.setConverter(new WireConverter());

    View full-size slide

  20. Pluggable client and serialization
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    ContributorResponse repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    builder.setConverter(new ProtoConverter());
    builder.setConverter(new WireConverter());
    builder.setConverter(new SimpleXMLConverter());

    View full-size slide

  21. Pluggable client and serialization
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    ContributorResponse repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    builder.setConverter(new ProtoConverter());
    builder.setConverter(new WireConverter());
    builder.setConverter(new SimpleXMLConverter());
    builder.setConverter(new CustomConverter());

    View full-size slide

  22. The Good
    • Interface service declarations
    • Method and parameter annotations customize request
    • Pluggable HTTP client and serialization
    • Synchronous, asynchronous, and RxJava execution

    View full-size slide

  23. Sync, async, and RxJava
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    List repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    List contributors =
    gitHubService.repoContributors("square", "retrofit");

    View full-size slide

  24. Sync, async, and RxJava
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    void repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo,

    Callback> cb);

    }
    service.repoContributors("square", "retrofit", new Callback>() {

    @Override void success(List contributors, Response response) {

    // ...

    }


    @Override void failure(RetrofitError error) {

    // ...

    }

    });

    View full-size slide

  25. Sync, async, and RxJava
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    gitHubService.repoContributors("square", "retrofit")

    .subscribe(new Action1>() {

    @Override public void call(List contributors) {

    // ...

    }

    });

    View full-size slide

  26. The Good
    • Interface service declarations
    • Method and parameter annotations customize request
    • Pluggable HTTP client and serialization
    • Synchronous, asynchronous, and RxJava execution

    View full-size slide

  27. The Not-So-Good

    View full-size slide

  28. The Not-So-Good
    • Request/Response (and friends) model classes

    View full-size slide

  29. Request/Response model classes

    View full-size slide

  30. The Not-So-Good
    • Request/Response (and friends) model classes
    • No access to serialized body and response data

    View full-size slide

  31. No access to body and response
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    List repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }X

    View full-size slide

  32. No access to body and response
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    List repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    @GET("/repos/{owner}/{repo}/contributors")

    Response repoContributors2(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }X

    View full-size slide

  33. The Not-So-Good
    • Request/Response (and friends) model classes
    • No access to serialized body and response data
    • Rigid execution mechanisms

    View full-size slide

  34. Rigid execution mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    List repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);
    @GET("/repos/{owner}/{repo}/contributors")

    void repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo,

    Callback> cb);

    }

    View full-size slide

  35. Rigid execution mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  36. Rigid execution mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    ListenableFuture> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  37. Rigid execution mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    CompletableFuture> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  38. The Not-So-Good
    • Request/Response (and friends) model classes
    • No access to serialized body and response data
    • Rigid execution mechanisms
    • Inefficient converter usage

    View full-size slide

  39. Inefficient converter usage
    interface Converter {

    Object fromBody(TypedInput body, Type type);

    TypedOutput toBody(Object object);

    }

    View full-size slide

  40. Inefficient converter usage
    interface Converter {

    Object fromBody(TypedInput body, Type type);

    TypedOutput toBody(Object object);

    }
    Type
    TypeAdapter
    TypedInput/Output
    JsonAdapter
    ObjectReader/Writer
    Parser
    ProtoAdapter

    View full-size slide

  41. Inefficient converter usage
    interface Converter {

    Object fromBody(TypedInput body, Type type);

    TypedOutput toBody(Object object);

    }
    Type
    TypeAdapter
    TypedInput/Output
    JsonAdapter
    ObjectReader/Writer
    Parser
    ProtoAdapter

    View full-size slide

  42. The Not-So-Good
    • Request/Response (and friends) model classes
    • No access to serialized body and response data
    • Rigid execution mechanisms
    • Inefficient converter usage
    • Simple custom parameter types support

    View full-size slide

  43. Simple custom parameter types
    interface GitHubService {

    @GET("/search/repositories")

    RepositoriesResponse searchRepos(

    @Query("q") String query,

    @Query("since") Date since);

    }X

    View full-size slide

  44. Simple custom parameter types
    interface GitHubService {

    @GET("/search/repositories")

    RepositoriesResponse searchRepos(

    @Query("q") String query,

    @Query("since") Date since);

    }X
    /search/repositories?q=retrofit&since=2015-08-27

    View full-size slide

  45. Simple custom parameter types
    interface GitHubService {

    @GET("/search/repositories")

    RepositoriesResponse searchRepos(

    @Query("q") String query,

    @Query("since") Date since);

    }X
    /search/repositories?q=retrofit&since=2015-08-27
    /search/repositories?q=retrofit&since=20150827

    View full-size slide

  46. The Not-So-Good
    • Request/Response (and friends) model classes
    • No access to serialized body and response data
    • Rigid execution mechanisms
    • Inefficient converter usage
    • Simple custom parameter types support

    View full-size slide

  47. Retrofit 2
    • Call encapsulates single request/response interaction

    View full-size slide

  48. Call
    • Models a single request/response pair

    View full-size slide

  49. Call
    • Models a single request/response pair
    • Separates request creation from response handling

    View full-size slide

  50. Call
    • Models a single request/response pair
    • Separates request creation from response handling
    • Each instance can only be used once...

    View full-size slide

  51. Call
    • Models a single request/response pair
    • Separates request creation from response handling
    • Each instance can only be used once...
    • ...instances can be cloned

    View full-size slide

  52. Call
    • Models a single request/response pair
    • Separates request creation from response handling
    • Each instance can only be used once...
    • ...instances can be cloned
    • Supports both synchronous and asynchronous execution.

    View full-size slide

  53. Call
    • Models a single request/response pair
    • Separates request creation from response handling
    • Each instance can only be used once...
    • ...instances can be cloned
    • Supports both synchronous and asynchronous execution.
    • Can be (actually) canceled

    View full-size slide

  54. Call
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    List repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);
    }X

    View full-size slide

  55. Call
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }X

    View full-size slide

  56. Call
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  57. Call
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    Call> call =
    gitHubService.repoContributors("square", "retrofit");

    View full-size slide

  58. Call
    Call> call =
    gitHubService.repoContributors("square", "retrofit");

    View full-size slide

  59. Call
    Call> call =
    gitHubService.repoContributors("square", "retrofit");

    response = call.execute();

    View full-size slide

  60. Call
    Call> call =
    gitHubService.repoContributors("square", "retrofit");

    response = call.execute();
    // This will throw IllegalStateException:

    response = call.execute();

    View full-size slide

  61. Call
    Call> call =
    gitHubService.repoContributors("square", "retrofit");

    response = call.execute();
    // This will throw IllegalStateException:

    response = call.execute();
    Call> call2 = call.clone();

    // This will not throw:

    response = call2.execute();

    View full-size slide

  62. Call
    Call> call =
    gitHubService.repoContributors("square", "retrofit");

    View full-size slide

  63. Call
    Call> call =
    gitHubService.repoContributors("square", "retrofit");

    call.enqueue(new Callback>() {

    @Override void onResponse(/* ... */) {

    // ...

    }


    @Override void onFailure(Throwable t) {

    // ...

    }

    });

    View full-size slide

  64. Call
    Call> call =
    gitHubService.repoContributors("square", "retrofit");
    call.enqueue( );
    // or...
    call.execute();

    View full-size slide

  65. Call
    Call> call =
    gitHubService.repoContributors("square", "retrofit");
    call.enqueue( );
    // or...
    call.execute();
    // later...
    call.cancel();

    View full-size slide

  66. Retrofit 2
    • Call encapsulates single request/response interaction
    • Parameterized Response object

    View full-size slide

  67. Response
    Call> call =
    gitHubService.repoContributors("square", "retrofit");
    response = call.execute();

    View full-size slide

  68. Response
    Call> call =
    gitHubService.repoContributors("square", "retrofit");
    Response> response = call.execute();

    View full-size slide

  69. Response
    Call> call =
    gitHubService.repoContributors("square", "retrofit");
    Response> response = call.execute();

    View full-size slide

  70. Response
    Call> call =
    gitHubService.repoContributors("square", "retrofit");
    call.enqueue(new Callback>() {

    @Override void onResponse(/* ... */) {

    // ...

    }X


    @Override void failure(Throwable t) {

    // ...

    }X

    });

    View full-size slide

  71. Response
    Call> call =
    gitHubService.repoContributors("square", "retrofit");
    call.enqueue(new Callback>() {

    @Override void onResponse(Response> response) {

    // ...

    }X


    @Override void failure(Throwable t) {

    // ...

    }X

    });

    View full-size slide

  72. Response
    Call> call =
    gitHubService.repoContributors("square", "retrofit");
    call.enqueue(new Callback>() {

    @Override void onResponse(Response> response) {

    // ...

    }


    @Override void failure(Throwable t) {

    // ...

    }

    });

    View full-size slide

  73. Response
    class Response {

    }X

    View full-size slide

  74. Response
    class Response {

    int code();

    String message();
    Headers headers();

    }X

    View full-size slide

  75. Response
    class Response {

    int code();

    String message();
    Headers headers();


    boolean isSuccess();

    }X

    View full-size slide

  76. Response
    class Response {

    int code();

    String message();
    Headers headers();


    boolean isSuccess();
    T body();

    ResponseBody errorBody();

    }X

    View full-size slide

  77. Response
    class Response {

    int code();

    String message();
    Headers headers();


    boolean isSuccess();
    T body();

    ResponseBody errorBody();
    com.squareup.okhttp.Response raw();

    }X

    View full-size slide

  78. Retrofit 2
    • Call encapsulates single request/response interaction
    • Parameterized Response object

    View full-size slide

  79. If it isn't broken...

    View full-size slide

  80. Fixed Query Param
    interface SomeService {

    @GET("/some/endpoint?fixed=query")

    Call someEndpoint();

    }
    someService.someEndpoint();
    // GET /some/endpoint?fixed=query HTTP/1.1

    View full-size slide

  81. Dynamic Query Param
    interface SomeService {

    @GET("/some/endpoint")

    Call someEndpoint(

    @Query("dynamic") String dynamic);

    }
    someService.someEndpoint("query");
    // GET /some/endpoint?dynamic=query HTTP/1.1

    View full-size slide

  82. Dynamic Query Param Map
    interface SomeService {

    @GET("/some/endpoint")

    Call someEndpoint(

    @QueryMap Map dynamic);

    }
    someService.someEndpoint(
    Collections.singletonMap("dynamic", "query"));
    // GET /some/endpoint?dynamic=query HTTP/1.1

    View full-size slide

  83. Omit Dynamic Query Param
    interface SomeService {

    @GET("/some/endpoint")

    Call someEndpoint(

    @Query("dynamic") String dynamic);

    }
    someService.someEndpoint(null);
    // GET /some/endpoint HTTP/1.1

    View full-size slide

  84. Fixed+Dynamic Query Params
    interface SomeService {

    @GET("/some/endpoint?fixed=query")

    Call someEndpoint(

    @Query("dynamic") String dynamic);

    }
    someService.someEndpoint("query");
    // GET /some/endpoint?fixed=query&dynamic=query HTTP/1.1

    View full-size slide

  85. Path Replacement
    interface SomeService {

    @GET("/some/endpoint/{foo}")

    Call someEndpoint(

    @Path("thing") String thing);

    }
    someService.someEndpoint("bar");
    // GET /some/endpoint/bar HTTP/1.1

    View full-size slide

  86. Fixed Header
    interface SomeService {

    @GET("/some/endpoint")
    @Headers("Accept-Encoding: application/json")

    Call someEndpoint();

    }
    someService.someEndpoint();
    // GET /some/endpoint HTTP/1.1
    // Accept-Encoding: application/json

    View full-size slide

  87. Dynamic Header
    interface SomeService {

    @GET("/some/endpoint")

    Call someEndpoint(
    @Header("Location") String location);

    }
    someService.someEndpoint("Droidcon NYC 2015");
    // GET /some/endpoint HTTP/1.1
    // Location: Droidcon NYC 2015

    View full-size slide

  88. Omit Dynamic Header
    interface SomeService {

    @GET("/some/endpoint")

    Call someEndpoint(
    @Header("Location") String location);

    }
    someService.someEndpoint(null);
    // GET /some/endpoint HTTP/1.1

    View full-size slide

  89. Fixed+Dynamic Header
    interface SomeService {

    @GET("/some/endpoint")
    @Headers("Accept-Encoding: application/json")

    Call someEndpoint(
    @Header("Location") String location);

    }
    someService.someEndpoint("Droidcon NYC 2015");
    // GET /some/endpoint HTTP/1.1
    // Accept-Encoding: application/json
    // Location: Droidcon NYC 2015

    View full-size slide

  90. Post Without Body
    interface SomeService {

    @POST("/some/endpoint")

    Call someEndpoint();

    }
    someService.someEndpoint();
    // POST /some/endpoint?fixed=query HTTP/1.1
    // Content-Length: 0

    View full-size slide

  91. Post With Body
    interface SomeService {

    @POST("/some/endpoint")

    Call someEndpoint(
    @Body SomeRequest body);

    }
    someService.someEndpoint();
    // POST /some/endpoint HTTP/1.1
    // Content-Length: 3
    // Content-Type: greeting
    //
    // Hi!

    View full-size slide

  92. Form Encoded Fields
    interface SomeService {

    @FormUrlEncoded
    @POST("/some/endpoint")

    Call someEndpoint(
    @Field("name1") String name1,
    @Field("name2") String name2);

    }
    someService.someEndpoint("value1", "value2");
    // POST /some/endpoint HTTP/1.1
    // Content-Length: 25
    // Content-Type: application/x-www-form-urlencoded
    //
    // name1=value1&name2=value2

    View full-size slide

  93. Omit Form Encoded Field
    interface SomeService {

    @FormUrlEncoded
    @POST("/some/endpoint")

    Call someEndpoint(
    @Field("name1") String name1,
    @Field("name2") String name2);

    }
    someService.someEndpoint("value1", null);
    // POST /some/endpoint HTTP/1.1
    // Content-Length: 12
    // Content-Type: application/x-www-form-urlencoded
    //
    // name1=value1

    View full-size slide

  94. Form Encoded Field Map
    interface SomeService {

    @FormUrlEncoded
    @POST("/some/endpoint")

    Call someEndpoint(
    @FieldMap Map names);

    }
    someService.someEndpoint(
    ImmutableMap.of("name1", "value1", "name2", "value2"));
    // POST /some/endpoint HTTP/1.1
    // Content-Length: 25
    // Content-Type: application/x-www-form-urlencoded
    //
    // name1=value1&name2=value2

    View full-size slide

  95. Multipart Parts
    interface SomeService {

    @Multipart
    @POST("/some/endpoint")

    Call someEndpoint(
    @Part("name1") String name1,
    @Part("name2") String name2);

    }
    someService.someEndpoint("value1", "value2");
    // POST /some/endpoint HTTP/1.1
    // Content-Length: 102
    // Content-Type: application/form-data
    //
    // ...

    View full-size slide

  96. Omit Multipart Part
    interface SomeService {

    @Multipart
    @POST("/some/endpoint")

    Call someEndpoint(
    @Part("name1") String name1,
    @Part("name2") String name2);

    }
    someService.someEndpoint("value1", null);
    // POST /some/endpoint HTTP/1.1
    // Content-Length: 56
    // Content-Type: application/form-data
    //
    // ...

    View full-size slide

  97. Multipart Part Map
    interface SomeService {

    @Multipart
    @POST("/some/endpoint")

    Call someEndpoint(
    @PartMap Map names);

    }
    someService.someEndpoint(
    ImmutableMap.of("name1", "value1", "name2", "value2"));
    // POST /some/endpoint HTTP/1.1
    // Content-Length: 102
    // Content-Type: application/form-data
    //
    // ...

    View full-size slide

  98. If it isn't broken...

    View full-size slide

  99. Retrofit 2
    • Call encapsulates single request/response interaction
    • Parameterized Response object
    • Dynamic URL parameter

    View full-size slide

  100. Dynamic URL
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  101. Dynamic URL
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    Call> call =
    gitHubService.repoContributors("square", "retrofit");
    Response> response = call.execute();

    View full-size slide

  102. Dynamic URL
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    Call> call =
    gitHubService.repoContributors("square", "retrofit");
    Response> response = call.execute();
    // HTTP/1.1 200 OK
    // Link: page=2>; rel="next", contributors?page=3>; rel="last"
    // ...

    View full-size slide

  103. Dynamic URL
    Response> response = call.execute();
    // HTTP/1.1 200 OK
    // Link: page=2>; rel="next", contributors?page=3>; rel="last"
    // ...

    View full-size slide

  104. Dynamic URL
    Response> response = call.execute();
    // HTTP/1.1 200 OK
    // Link: page=2>; rel="next", contributors?page=3>; rel="last"
    // ...
    String links = response.headers().get("Link");

    View full-size slide

  105. Dynamic URL
    Response> response = call.execute();
    // HTTP/1.1 200 OK
    // Link: page=2>; rel="next", contributors?page=3>; rel="last"
    // ...
    String links = response.headers().get("Link");
    String nextLink = nextFromGitHubLinks(links);

    View full-size slide

  106. Dynamic URL
    Response> response = call.execute();
    // HTTP/1.1 200 OK
    // Link: page=2>; rel="next", contributors?page=3>; rel="last"
    // ...
    String links = response.headers().get("Link");
    String nextLink = nextFromGitHubLinks(links);
    // https://api.github.com/repositories/892275/contributors?page=2

    View full-size slide

  107. Dynamic URL
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }X

    View full-size slide

  108. Dynamic URL
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    @GET

    Call> repoContributorsPaginate(

    @Url String url);

    }X

    View full-size slide

  109. Dynamic URL
    String nextLink = nextFromGitHubLinks(links);
    // https://api.github.com/repositories/892275/contributors?page=2

    View full-size slide

  110. Dynamic URL
    String nextLink = nextFromGitHubLinks(links);
    // https://api.github.com/repositories/892275/contributors?page=2
    Call> nextCall =
    gitHubService.repoContributorsPaginate(nextLink);

    View full-size slide

  111. Retrofit 2
    • Call encapsulates single request/response interaction
    • Parameterized Response object
    • Dynamic URL parameter
    • Multiple, efficient converters

    View full-size slide

  112. Converters
    interface SomeProtoService {

    @GET("/some/proto/endpoint")

    Call someProtoEndpoint();
    }X
    interface SomeJsonService {

    @GET("/some/json/endpoint")

    Call someJsonEndpoint();

    }X

    View full-size slide

  113. Converters
    interface SomeService {

    @GET("/some/proto/endpoint")

    Call someProtoEndpoint();

    @GET("/some/json/endpoint")

    Call someJsonEndpoint();

    }X


    SomeProtoResponse

    View full-size slide

  114. Converters
    interface SomeService {

    @GET("/some/proto/endpoint")

    Call someProtoEndpoint();

    @GET("/some/json/endpoint")

    Call someJsonEndpoint();

    }X
    SomeProtoResponse

    View full-size slide

  115. Converters
    interface SomeService {

    @GET("/some/proto/endpoint")

    Call someProtoEndpoint();

    @GET("/some/json/endpoint")

    Call someJsonEndpoint();

    }X
    SomeProtoResponse Proto? Yes!

    View full-size slide

  116. Converters
    interface SomeService {

    @GET("/some/proto/endpoint")

    Call someProtoEndpoint();

    @GET("/some/json/endpoint")

    Call someJsonEndpoint();

    }X
    SomeJsonResponse

    View full-size slide

  117. Converters
    interface SomeService {

    @GET("/some/proto/endpoint")

    Call someProtoEndpoint();

    @GET("/some/json/endpoint")

    Call someJsonEndpoint();

    }X
    SomeJsonResponse Proto? No!

    View full-size slide

  118. Converters
    interface SomeService {

    @GET("/some/proto/endpoint")

    Call someProtoEndpoint();

    @GET("/some/json/endpoint")

    Call someJsonEndpoint();

    }X
    SomeJsonResponse Proto? No! JSON? Yes!

    View full-size slide

  119. Retrofit 2
    • Call encapsulates single request/response interaction
    • Parameterized Response object
    • Dynamic URL parameter
    • Multiple, efficient converters
    • Multiple, pluggable execution mechanisms

    View full-size slide

  120. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }X

    View full-size slide

  121. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(

    @Path("owner") String owner,

    @Path("repo") String repo);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }X

    View full-size slide

  122. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(..);

    }X
    Call

    View full-size slide

  123. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(..);

    }X
    Call

    View full-size slide

  124. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(..);

    }X
    Call RxJava? No!

    View full-size slide

  125. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(..);

    }X
    Call RxJava? No! Call? Yes!

    View full-size slide

  126. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(..);

    }X
    Observable

    View full-size slide

  127. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(..);

    }X
    Observable RxJava? Yes!

    View full-size slide

  128. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(..);

    }X
    Future

    View full-size slide

  129. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(..);

    }X
    Future RxJava? No!

    View full-size slide

  130. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(..);

    }X
    Future RxJava? No! Call? No!

    View full-size slide

  131. Execution Mechanisms
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Future> repoContributors3(..);

    }X
    Future RxJava? No! Call? No! Throw!

    View full-size slide

  132. Retrofit 2
    • Call encapsulates single request/response interaction
    • Parameterized Response object
    • Dynamic URL parameter
    • Multiple, efficient converters
    • Multiple, pluggable execution mechanisms

    View full-size slide

  133. Powered by OkHttp

    View full-size slide

  134. Powered by OkHttp
    • In 2012 we needed client abstractions.

    View full-size slide

  135. Powered by OkHttp
    • In 2012 we needed client abstractions.
    • In 2012 we needed request/response abstractions.

    View full-size slide

  136. Powered by OkHttp
    • In 2012 we needed client abstractions.
    • In 2012 we needed request/response abstractions.
    • In 2012 we needed header abstractions.

    View full-size slide

  137. Powered by OkHttp
    • In 2012 we needed client abstractions.
    • In 2012 we needed request/response abstractions.
    • In 2012 we needed header abstractions.
    • It's 2015. OkHttp is small, lean, focused, and full-featured.

    View full-size slide

  138. Powered by OkHttp

    View full-size slide

  139. Powered by OkHttp & Okio
    bit.ly/ok-libs

    View full-size slide

  140. Retrofit OkHttp

    View full-size slide

  141. Retrofit OkHttp
    Socket

    View full-size slide

  142. Retrofit OkHttp
    BufferedSource
    BufferedSink
    Socket

    View full-size slide

  143. Retrofit OkHttp
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody
    Socket

    View full-size slide

  144. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody
    Socket

    View full-size slide

  145. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody
    Socket
    Moshi

    View full-size slide

  146. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody
    Socket
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  147. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    RequestBody
    ResponseBody
    Socket
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  148. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  149. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  150. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  151. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  152. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  153. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  154. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  155. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  156. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  157. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  158. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  159. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  160. Retrofit OkHttp
    MoshiConverter
    BufferedSource
    BufferedSink
    RequestBody
    ResponseBody

    MyService
    RequestBody
    ResponseBody
    Socket
    execute()
    myEndpoint()
    Moshi
    BufferedSink
    BufferedSource

    View full-size slide

  161. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();

    View full-size slide

  162. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  163. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    GitHubService gitHubService = retrofit.create(GitHubService.class);

    View full-size slide

  164. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    // https://api.github.com/repos/square/retrofit/contributors

    View full-size slide

  165. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    // https://api.github.com/repos/square/retrofit/contributors
    HttpUrl

    View full-size slide

  166. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    // https://api.github.com/repos/square/retrofit/contributors
    HttpUrl
    .resolve()

    View full-size slide

  167. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  168. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/v3/")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  169. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/v3/")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  170. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/v3/")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    // https://api.github.com/repos/square/retrofit/contributors

    View full-size slide

  171. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/v3/")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    // https://api.github.com/repos/square/retrofit/contributors
    HttpUrl
    .resolve()

    View full-size slide

  172. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/v3/")
    .build();
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }X

    View full-size slide

  173. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/v3/")
    .build();
    interface GitHubService {

    @GET("repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }X

    View full-size slide

  174. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/v3/")
    .build();
    interface GitHubService {

    @GET("repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }

    View full-size slide

  175. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/v3/")
    .build();
    interface GitHubService {

    @GET("repos/{owner}/{repo}/contributors")

    Call> repoContributors(

    @Path("owner") String owner,

    @Path("repo") String repo);

    }
    // https://api.github.com/v3/repos/square/retrofit/contributors

    View full-size slide

  176. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();

    View full-size slide

  177. Setup
    OkHttpClient client = new OkHttpClient();
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .client(client)
    .build();

    View full-size slide

  178. Setup
    OkHttpClient client = new OkHttpClient();
    client.interceptors().add(..);
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .client(client)
    .build();

    View full-size slide

  179. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();

    View full-size slide

  180. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

    View full-size slide

  181. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .addConverterFactory(GsonConverterFactory.create())
    .addConverterFactory(ProtoConverterFactory.create())
    .build();

    View full-size slide

  182. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .addConverterFactory(ProtoConverterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .build();

    View full-size slide

  183. Setup
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .addConverterFactory(ProtoConverterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .build();

    View full-size slide

  184. Extensibility
    • Converter.Factory
    • CallAdapter.Factory

    View full-size slide

  185. Converter.Factory
    interface SomeService {

    @GET("/some/proto/endpoint")

    Call someProtoEndpoint();

    @GET("/some/json/endpoint")

    Call someJsonEndpoint();

    }X
    SomeJsonResponse Proto? No! JSON? Yes!

    View full-size slide

  186. Converter.Factory
    interface Converter {
    interface Factory {
    Converter> create(Type type);
    }
    }X
    SomeJsonResponse Proto? No! JSON? Yes!

    View full-size slide

  187. Converter.Factory
    SomeJsonResponse Proto? No! JSON? Yes!

    View full-size slide

  188. Converter.Factory
    class ProtoConverterFactory {
    Converter> create(Type type);
    }X
    SomeJsonResponse
    null No!
    JSON? Yes!

    View full-size slide

  189. JSON
    Converter.Factory
    class ProtoConverterFactory {
    Converter> create(Type type);
    }X
    SomeJsonResponse
    null No!
    Converter> Yes!
    class GsonConverterFactory {
    Converter> create(Type type);
    }X

    View full-size slide

  190. Converter.Factory
    interface Converter {
    interface Factory {
    Converter> create(Type type);
    }X
    }X

    View full-size slide

  191. Converter.Factory
    interface Converter {
    interface Factory {
    Converter> create(Type type);
    }X
    T fromBody(ResponseBody body);
    RequestBody toBody(T value);
    }X

    View full-size slide

  192. Extensibility
    • Converter.Factory
    • CallAdapter.Factory

    View full-size slide

  193. CallAdapter.Factory
    interface GitHubService {

    @GET("/repos/{owner}/{repo}/contributors")

    Call> repoContributors(..);

    @GET("/repos/{owner}/{repo}/contributors")

    Observable> repoContributors2(..);

    }X
    Call RxJava? No! Call? Yes!

    View full-size slide

  194. CallAdapter.Factory
    interface CallAdapter {
    interface Factory {
    CallAdapter> create(Type type);
    }

    }X
    Call RxJava? No! Call? Yes!

    View full-size slide

  195. CallAdapter.Factory
    Call RxJava? No! Call? Yes!

    View full-size slide

  196. CallAdapter.Factory
    Call Call? Yes!
    class RxJavaCallAdapterFactory {
    CallAdapter> create(Type type);
    }X
    null No!

    View full-size slide

  197. CallAdapter.Factory
    Call
    class RxJavaCallAdapterFactory {
    CallAdapter> create(Type type);
    }X
    null No!
    JSON
    CallAdapter> Yes!
    class CallAdapterFactory {
    CallAdapter> create(Type type);
    }X

    View full-size slide

  198. CallAdapter.Factory
    interface CallAdapter {
    interface Factory {
    CallAdapter> create(Type type);
    }X
    }X

    View full-size slide

  199. CallAdapter.Factory
    interface CallAdapter {
    interface Factory {
    CallAdapter> create(Type type);
    }X
    Type responseType();
    Object adapt(Call value);
    }X

    View full-size slide

  200. Extensibility
    • Converter.Factory
    • CallAdapter.Factory

    View full-size slide

  201. Under Construction

    View full-size slide

  202. Under Construction
    • Parameter handlers

    View full-size slide

  203. Under Construction
    • Parameter handlers
    • Logging?

    View full-size slide

  204. Under Construction
    • Parameter handlers
    • Logging?
    • Finalizing mock module

    View full-size slide

  205. Under Construction
    • Parameter handlers
    • Logging?
    • Finalizing mock module
    • Documentation

    View full-size slide

  206. Under Construction
    • Parameter handlers
    • Logging?
    • Finalizing mock module
    • Documentation
    • WebSockets! (in v2.1)

    View full-size slide

  207. Release?
    dependencies {

    compile 'com.squareup.retrofit:retrofit:2.0.0-beta1'
    compile 'com.squareup.retrofit:converter-gson:2.0.0-beta1'
    compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta1'
    }

    View full-size slide

  208. jakewharton
    jakewharton
    twitter.com/
    google.com/+
    .com
    Retrofit Two
    jakewharton

    View full-size slide