Retrofit REST client for Android

July 17, 2014 Phil Goodwin

Introdution

I recently started a beach project from scratch and wanted to get a quick jump on connecting to the RESTful backend we’d already started developing for the project. On past Android projects I’ve always pulled the ApiGateway code out of the RobolectricSample application that Tyler Schultz wrote in 2010. It’s always worked well but has also taken a significant amount of rework for each project, so we decided to look around to see if there were any open source alternatives available. We found a promising library called Retrofit from Square. Square has done significant work to revise and maintain Robolectric and has also produced some other useful Android libraries (Otto and Dagger come to mind), so I was eager to check out Retrofit. We pulled it down and after having used it for a couple of weeks we’ve found it to be a solid addition to our toolbox.

It took a bit of work, however, to get it running well for us, especially when it came to testing. We faced two large challenges. The first is that it bypasses Robolectric’s mechanism for mocking out HTTP calls so we were unable to spy on them and control their responses. After we got around that we found that our responses were being handled on a worker thread, which is what we wanted in production, but meant that our tests were completing before the responses could be handled. After a brief description of how Retrofit works we’ll take a look at what it takes to get it installed and working well with RoboGuice and a test suite.

The idea behind Retrofit is to describe a REST api using a Java interface and some annotations and then let the Retrofit generator create a client that supports that interface using a DynamicProxy. The interface we wrote looks something like this:

public interface PostFollowersService {
  Converter DATA_CONVERTER = new GsonConverter(new Gson());
  String SERVICE_ENDPOINT = "http://10.0.2.2:3000";

  @POST("/api/v1/user")
  void signUp(@Body SignUpLoginRequestBody request, Callback callback);

  @GET("/api/v1/user")
  void logIn(@Query("name") String name, Callback callback);

  @POST("/api/v1/user/{id}/posts")
  void addPost(
    @Path("id") int id,
    @Body UserPostRequestBody request,
    Callback callback);

  @DELETE("/api/v1/user/{id}")
  void deleteUser(@Path("id") int id, Callback callback);
}

The annotation language allows us to specify the path to send the request to as well as where to put the data we’re sending. There is a builder API that allows us to specify the URL for the host and exactly how to encode the data. To create a working client for our local Rails based service using Gson to convert between Java objects and JSON we can make the following calls:

RestAdapter.Builder adapterBuilder = new RestAdapter.Builder();
adapterBuilder
  .setConverter(new GsonConverter(Json.GSON))
  .setEndpoint("http://10.0.2.2:3000");

RestAdapter adapter = adapterBuilder.build();

PostFollowersService service =
  adapter.create(PostFollowersService.class);

The adapterBuilder is the configuration entry point for Retrofit. Once we’ve made one of these and told it the location of our server and the

Installation

We’re using Maven for our project management, so we’re going to use that to pull Retrofit into the project. We’re planning to use RoboGuice to configure Retrofit for use in our test suite so we’ll pull that in too by putting the following lines in our top level pom.xml file:

<dependency>
  <groupId>org.roboguice</groupId>
  <artifactId>roboguice</artifactId>
  <version>2.0</version>
</dependency>
<dependency>
  <groupId>com.squareup.retrofit</groupId>
  <artifactId>retrofit</artifactId>
  <version>1.6.1</version>
</dependency>

Configuration for production code

The entry point for RoboGuice configuration is an XML file. It is virtually impossible to get around this limitation. There is an API to customize the location of this file, but none to supply the data it contains (which is just the path to the Java class containing the configuration data). The default location of this file is:

<Project Root>/res/values/roboguice.xml

The contents of our roboguice.xml is:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string-array name="roboguice_modules">
    <item>
      com.pivotallabs.post_followers.injection.PostFollowersModule
    </item>
  </string-array>
</resources>

Which just says that our RoboGuice configuration is in PostFollowersModule.java. The relevant part of our module is shown below. It sets up a provider for each collaborator in the sequence which produces services (including the service itself):

public class PostFollowersModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(RestAdapter.Builder.class)
      .toProvider(RestAdapterBuilderProvider.class)
      .asEagerSingleton();
    bind(PostFollowersService.class)
      .toProvider(PostFollowersServiceProvider.class)
      .asEagerSingleton();
  }
}

Our application only uses the binding for PostFollowersService, the binding for RestAdapter.Builder is there for us to override for tests. The default implementation is very straight forward, it just creates a new builder and returns it:

class RestAdapterBuilderProvider implements Provider {
    @Override
    public RestAdapter.Builder get() {
        return new RestAdapter.Builder();
    }
}

The implementation for the service is a little more complicated:

public class PostFollowersServiceProvider implements Provider {
    @Inject
    RestAdapter.Builder builder;

    @Override
    public PostFollowersService get() {
        return builder
                .setConverter(PostFollowersService.DATA_CONVERTER)
                .setEndpoint(PostFollowersService.SERVICE_ENDPOINT)
                .build()
                .create(PostFollowersService.class);
    }
}

It starts out by injecting the builder. This is so that when we’re running tests we’ll get the builder that the test framework sets up. After that we configure the builder with a strategy object to convert between Java objects and JSON, and the host and port that we want to talk to. Finally, we create the RestAdapter and use it to produce the REST client object that implements our service.

Now we can @Inject a PostFollwersService anywhere we want to make a call to our API. For example in our main activity we want to show a list of posts, so we make the following call in our onResume():

  apiService.getUserPosts(loggedInUserId, new Callback() {
    @Override
    public void success(PostListResponse postListResponse, Response response) {
      UserPostListAdapter userPostListAdapter
        = new UserPostListAdapter(postListResponse.getPosts());
      userPostListView.setAdapter(userPostListAdapter);
    }
    @Override
    public void failure(RetrofitError error) {
  });

Test Configuration
Robolectric can be used to override the RoboGuice configuration module, but the mechanism to do so is even more well hidden than the RoboGuice entry point. To accomplish it we create an Application class in the test tree at the same location as the project’s Application and with the same name prepended with the word “Test”. Robolectric will search for this class on startup and, if it implements an interface called “TestLifecycleApplication”, it will give this class an opportunity to instrument tests before they are run. We use this mechanism to override the applications bindings with test bindings like this:

public class TestPostFollowersApplication
  extends PostFollowersApplication
  implements TestLifecycleApplication {
    @Override public void beforeTest(Method method) {}
    @Override public void prepareTest(Object test) {
      Injector injector = RoboGuice.setBaseApplicationInjector(
        this,
        RoboGuice.DEFAULT_STAGE,
        RoboGuice.newDefaultRoboModule(this),
        Modules.override(new PostFollowersModule())
          .with(new PostFollowersTestModule()));
      injector.injectMembers(test);
    }
    @Override public void afterTest(Method method) {}
}

The key part of this code is the part that says “override(new PostFollowersModule().with(new PostFollowersTestModule()))”. The rest is just there to make sure it gets called at the right time.

We are going to use this entry point to mock out the services’s HTTP calls and to make them all synchronous when running in tests (I may write a later blog post about how to test that the callbacks behave correctly when called asynchronously). The module that puts this infrastructure in place looks like this:

public class PostFollowersTestModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(TestClient.class).asEagerSingleton();
    bind(RestAdapter.Builder.class)
      .toProvider(TestRestAdapterBuilderProvider.class)
      .asEagerSingleton();
  }
}

The first binding sets up the HTTP mock. It’s a singleton so we can get access to it in our tests. The second binding substitutes in a specially configured RestAdapter.Builder. It looks like this:

class TestRestAdapterBuilderProvider implements Provider {
  @Inject TestClient client;
  @Override public RestAdapter.Builder get() {
    return new RestAdapter.Builder()
      .setExecutors(new Executor() {
        @Override public void execute(Runnable command) {
          command.run();
        }
      }, null)
      .setClient(client);
  }
}

The Executor causes the mocked out HTTP calls, including their callbacks, to execute immediately. This makes it a lot easier to ensure that those callbacks do the right thing in tests. We use injection to get the same singleton TestClient as we see in tests and install it as the handler for HTTP requests. A simplified version of our TestClient looks like this:

public class TestClient implements Client {
    private List requests = new ArrayList();
    private List cannedResponses = new ArrayList();

    @Override
    public Response execute(Request request) throws IOException {
        this.requests.add(request);
        if (cannedResponses.size() > 0) {
            return cannedResponses.remove(0);
        }
        return new Response("", 200, "", new ArrayList
(), new TypedString("")); } public void addResponse(Response response) { cannedResponses.add(response); } public Request getLastRequest() { return requests.size() > 0 ? getRequest(requests.size() - 1) : null; } public Request getRequest(int index) { return requests.get(index); } public static Response response(int status, String body) { return new Response("", status, "", new ArrayList
(), new TypedString(body)); } }

All it does is store up canned responses and feed them back through the Retrofit infrastructure whenever an HTTP call is executed.

Conclusion
As with many Android testing problems this took a surprisingly large amount of arcane knowledge and lines of code to solve, but the results for us so far have been promising. Once installed Retrofit has been easy to use and robust. It already supports a wider range of features than I’ve needed across my past Android projects and it is under active development. I am pleased to be bidding a fond farewell to ApiGateway and looking forward to a smoother relationship with HTTP based APIs on Android.

(The full source for the project used for this post can be found at https://github.com/pivotal/post-followers_sample-app.git in the “client” folder)

About the Author

Biography

Previous
Synchronizing 1000s of Mobile Phones with RabbitMQ
Synchronizing 1000s of Mobile Phones with RabbitMQ

Co-authored with Dan Buchko. Our team was recently involved in a project where potentially thousands of mob...

Next
Pay Transparency Matters
Pay Transparency Matters