home.


Tagged: android-retrofit


Android: Using OkHTTP's response cache (Retrofit)

If you’re using Refrofit with OkHTTP, or just OkHTTP on its own, you can set it up to cache your responses.

Update: There’s a simpler version of this for OkHTTP3 and Retrofit 2, although this shows you how to directly access the cache, which you may not need.

This is an example in OkHTTP 2.0.

OkHttpClient ok = new OkHttpClient();
try {
    Cache responseCache = new Cache(context.getCacheDir(), SIZE_OF_CACHE);
    ok.setCache(responseCache);
} catch (Exception e) {
    Log.d(TAG, "Unable to set http cache", e);
}
ok.setReadTimeout(30, TimeUnit.SECONDS);
ok.setConnectTimeout(30, TimeUnit.SECONDS);

If you cache is large enough, OkHTTP will start to cache your responses. And if your server uses Etags or similar, it will returned cached responses on 304s.

You can also access this cache to return cached responses before making a network response.

public FilterInputStream getFromCache(String url) throws Exception {
    DiskLruCache cache = DiskLruCache.open(context.getCacheDir(), 201105, 2, SIZE_OF_CACHE);    
    cache.flush();
    String key = Util.hash(url);
    final DiskLruCache.Snapshot snapshot;
    try {
        snapshot = cache.get(key);
        if (snapshot == null) {
            return null;
        }
    } catch (IOException e) {
        return null;
    }

    FilterInputStream bodyIn = new FilterInputStream(snapshot.getInputStream(1)) {
        @Override
        public void close() throws IOException {
            snapshot.close();
            super.close();
        }
    };

    return bodyIn;
}

This opens the DiskLruCache that OkHTTP uses internally, makes a hash out of your URL using an OkHTTP utility method, then returns the cache as a InputStream.

You must pass this the full URL used to make the request, including the query paramters, or the hashing won’t match the saved response.

The arguments to DiskLriCache.open() must match those used internally by OkHTTP, those used by the com.squareup.okhttp.Cache.java when you issued new Cache(context.getCacheDir(), SIZE_OF_CACHE). Accordingly, when you update OkHTTP this may break - warning!

Now you must convert the InputStream to your response object:

Scanner sc = new Scanner(filterInputStream);
String str="", s;
while(sc.hasNext() && (s=sc.nextLine())!=null) {
    str = str + s;
}
ReturnType recentPosts = new Gson().fromJson(str, returnType);

The ReturnType is the type of the response, a POJO used to deserialise the JSON in this case.

android android-retrofit android-okhttp

Android: Cache network requests for offline access with Retrofit2 and OkHTTP3

Let’s first build a OKHTTP client with

  1. a cache
  2. an interceptor that checks for connectivity and if none asks for cached data:

Here’s the client.

OkHttpClient client = new OkHttpClient
  .Builder()
  .cache(new Cache(App.sApp.getCacheDir(), 10 * 1024 * 1024)) // 10 MB
  .addInterceptor(new Interceptor() {
    @Override public Response intercept(Chain chain) throws IOException {
      Request request = chain.request();
      if (App.isNetworkAvailable()) {
        request = request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build();
      } else {
        request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 7).build();
      }
      return chain.proceed(request);
    }
  })
  .build();

We first create the cache object with 10 MB, getting the cache directory from a static Application context.

Then the Interceptor uses a utility method in my Application class to check for connectivity. If there is connectivity, we tell the request it can reuse the data for sixty seconds.

If there’s no connectivity, we ask to be given only (only-if-cached) ‘stale’ data upto 7 days ago.

Now make this OkHTTP client your client for Retrofit2 and you will be able to use your old cached data when the app goes offline.

android android-retrofit android-okhttp

Android, RxJava and Retrofit: Wait for multiple network calls to finish

Say you have multiple network calls you need to make–cals to get Github user information and Github user events for example.

And you want to wait for each to return before updating the UI. RxJava can help you here.

Let’s first define our Retrofit object to access Github’s API, then setup two observables for the two network requests above:

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

Observable<JsonObject> userObservable = repo
        .create(GitHubUser.class)
        .getUser(loginName)
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread());

Observable<JsonArray> eventsObservable = repo
        .create(GitHubEvents.class)
        .listEvents(loginName)
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread());

The Retrofit interfaces are simple enough:

public interface GitHubUser {
  @GET("users/{user}")
  Observable<JsonObject> getUser(@Path("user") String user);
}

public interface GitHubEvents {
  @GET("users/{user}/events")
  Observable<JsonArray> listEvents(@Path("user") String user);
}

Lately we use RxJava’s zip method to combine our two Observables and wait for them to complete before creating a new Observable.

Observable<UserAndEvents> combined = Observable.zip(userObservable, eventsObservable, new Func2<JsonObject, JsonArray, UserAndEvents>() {
  @Override
  public UserAndEvents call(JsonObject jsonObject, JsonArray jsonElements) {
    return new UserAndEvents(jsonObject, jsonElements);
  }
});

What’s the UserAndEvents? It’s just a simple POJO to combine the two objects:

public class UserAndEvents {
  public UserAndEvents(JsonObject user, JsonArray events) {
    this.events = events;
    this.user = user;
  }

  public JsonArray events;
  public JsonObject user;
}

Finally let’s call the subscribe method on our new combined Observable:

combined.subscribe(new Subscriber<UserAndEvents>() {
          ...
          @Override
          public void onNext(UserAndEvents o) {
            // You can access the results of the 
            // two observabes via the POJO now
          }
        });

No more waiting in threads etc for network calls to finish. RxJava has done all that for you in zip().

android android-rxjava android-retrofit

Android and Retrofit 2.0

Now Retrofit is at 2.0, there are a few basic changes to the usual approach. Here’s the interface which is as before:

public interface GitHubService {
  @GET("users/{user}")
  Call<User> listRepos(@Path("user") String user);
}

And here’s the basic builder to create the service:

final GitHubService service = new Retrofit.Builder()
        .baseUrl("https://api.github.com")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        .create(GitHubService.class);

But to get this we must now explictly include our Gson dependency in our gradle file:

compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'

And finally we call the repo and it returns a Call, and we call the enqueue method on that to get the data asynchronously.

Call<User> repos = service.listRepos("octocat");

repos.enqueue(new Callback<User>() {
  @Override
  public void onResponse(Call<User> call, Response<User> response) {
    User body = response.body(); // Body could be null if parse error
    Log.d("HIYA", "good " + body.avatar_url);
  }

  @Override
  public void onFailure(Call<User> call, Throwable t) {
    Log.d("HIYA", "error " + t.getMessage());
  }
});

The good thing about the new Call architecture if that we can check the headers and such on a valid response.

Our data object is as simple as ever:

private static class User {
  public String avatar_url;
}
android android-retrofit

Android: Retrofit and Otto

You can use Square’s Refrofit library, Square’s event bus Otto, along with Google’s Json converter Gson, can ease your REST calls.

First start off with an interface that has some Retrofit annotations specifying the service:

public static interface RecentPostsServiceInterface {
    @GET("/post/{start}/{num}")
    RecentPosts go(@Path("start") int start, @Path("num") int num);
}

Then create a RestAdapter, specifying the end point, the Gson converter and using our interface:

RestAdapter restAdapter = new RestAdapter.Builder()
        .setEndpoint("https://android-manchester.co.uk/api/rest")
        .setConverter(new GsonConverter(new Gson()))
        .build();
RecentPostsServiceInterface service = restAdapter.create(RecentPostsServiceInterface.class);

You can then call the following to get your result:

RecentPosts recentPosts = service.go(start, num);

You should really put this in a AsyncTask:

public class OurRestService {

  private static final String TAG = OurRestService.class.getSimpleName();

  public void fetch(
      final RecentPostsServiceInterface service, 
      final int start, 
      final int num) {
    new AsyncTask<Void, Void, RecentPosts>() {
        @Override
        protected ReturnResult doInBackground(Void... params) {
            try {
                Log.d(TAG, "Attempting to fetch result from base url: " + endPoint);
                RecentPosts res = service.go(start, num);
                if(res!=null) {
                    Log.d(TAG, "Fetched : " + res.toString() + " from " + endPoint);
                }
                return res;
            } catch (RetrofitError retroError) {
                // TODO
                return null;
            } catch(Exception e) {
                Log.e(TAG, "Unknown error", e);
                return null;
            }
        }
        @Override
        protected void onPostExecute(RecentPosts res) {
            // TODO
        }
    }.execute();
  }
}

One problem with this code is that it isn’t very generic - and we suspect we’ll be making lots of networking calls.

First let’s make the service return type generic:

public class OurRestService<ReturnType> {

    private static final String TAG = OurRestService.class.getSimpleName();

    public void fetch(
        final RecentPostsServiceInterface service, 
        final int start, 
        final int num) {
      new AsyncTask<Void, Void, ReturnType>() {
          @Override
          protected ReturnResult doInBackground(Void... params) {
              try {
                  Log.d(TAG, "Attempting to fetch result from base url: " + endPoint);
                  ReturnType res = service.go(start, num);
                  if(res!=null) {
                      Log.d(TAG, "Fetched : " + res.toString() + " from " + endPoint);
                  }
                  return res;
              } catch (RetrofitError retroError) {
                  // TODO
                  return null;
              } catch(Exception e) {
                  Log.e(TAG, "Unknown error", e);
                  return null;
              }
          }
          @Override
          protected void onPostExecute(ReturnType res) {
              // TODO
          }
      }.execute();
    }

  }

Next, since in our all we’ll be passing all kinds of arguments to our rest services, we need to stop passing the start and num parameters.

Instead, we’ll pass a callback, which will take in the service, and return the return type.

This means, therefore, the Retrofit service should be made generic, since we’ll be passing that into the callback.

We’ll create the callback as such:

    public interface class GetResult<ReturnType, ServiceClass>  {
      ReturnType getResult(ServiceClass service);
    }

In the implementation for this method, where we have access to start and num, we’d pass the parameters to the service and return it’s result.

    new GetResult<RecentPosts, RecentPostsServiceInterface>() {
      @Override public RecentPosts getResult(RecentPostsServiceInterface service) {
          return service.go(start, num);
      }
    }

Now our service is generic

  public class OurRestService<ReturnType, RestService> {

    private static final String TAG = OurRestService.class.getSimpleName();

    public static abstract class GetResult<ReturnResult, ServiceClass>  {
        public abstract ReturnResult getResult(ServiceClass mService);
    }

    public void fetch(
        final RestService service, 
        final GetResult getResult) {
      new AsyncTask<Void, Void, ReturnType>() {
          @Override
          protected ReturnResult doInBackground(Void... params) {
              try {
                  Log.d(TAG, "Attempting to fetch result from base url: " + endPoint);
                  ReturnType res = getResult.go(service);
                  if(res!=null) {
                      Log.d(TAG, "Fetched : " + res.toString() + " from " + endPoint);
                  }
                  return res;
              } catch (RetrofitError retroError) {
                  // TODO
                  return null;
              } catch(Exception e) {
                  Log.e(TAG, "Unknown error", e);
                  return null;
              }
          }
          @Override
          protected void onPostExecute(ReturnType res) {
              // TODO
          }
      }.execute();
    }

  }

But we’re not passing back the results to the UI thread.

One way to do this is to use an Event Bus, Otto in this case.

The event bus will attach itself to your fragment or activity on onResume and detact it on onPause. This means you won’t get result when the activity or fragment is no longer active.

This is how you initialise Otto, in your app’s Application class:

    public class Application extends android.app.Application {

        private static Bus sEventBus;

        public static Bus getEventBus() {
            if(sEventBus==null) {
                sEventBus = new com.squareup.otto.Bus();
            }
            return sEventBus;
        }

    }

Next in your fragment, say, this is how you subscribe and unsubscribe to events:

    @Override
    public void onPause() {
        super.onAttach(activity);
        Application.getEventBus().unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        Application.getEventBus().register(this);
    }

Finally, let’s subscribe to two events that we’ve not yet defined, one for the results and one for an error.

@Subscribe
public void onRecentPosts(RecentPostsService.RecentPosts posts) {
    // Do something
}

@Subscribe
public void onRecentPostsError(RecentPostsService.RecentPostsError error) {
    // Do something
}

We can send the first event, RecentPosts, easily enough. In our onPostExecute() method we can send the event up the event bus:

    ...
    protected void onPostExecute(ReturnType res) {
        if(res!=null) {
            Application.getEventBus().post(res);
        }
    }
    ...

Now, when you issue the fetch() call with the service and callback, when the service returns it will send the result up the event bus to your fragment or activity.

Sending an error object is a little tricker. In our call to fetch() we must pass in a generic error object, extended per service, fill it with errors from to Retrofit exception and pass that up the event bus.

With those changes, our class looks like this:

  public class OurRestService<ReturnType, RestService> {

    private static final String TAG = OurRestService.class.getSimpleName();

    public static abstract class GetResult<ReturnResult, ServiceClass>  {
        public abstract ReturnResult getResult(ServiceClass mService);
    }

    public void fetch(
        final RestService service, 
        final GetResult getResult,
        final ErrorResponse errorResponse, 
        ) {
      new AsyncTask<Void, Void, ReturnType>() {
          @Override
          protected ReturnResult doInBackground(Void... params) {
              try {
                  Log.d(TAG, "Attempting to fetch result from base url: " + endPoint);
                  ReturnType res = getResult.go(service);
                  if(res!=null) {
                      Log.d(TAG, "Fetched : " + res.toString() + " from " + endPoint);
                  }
                  return res;
              } catch (RetrofitError retroError) {
                errorResponse.fill(e.getResponse().getStatus(),
                                   e.getResponse().getReason(),
                                   e.getResponse().getUrl(),
                                   e.isNetworkError());
                  return null;
              } catch(Exception e) {
                  Log.e(TAG, "Unknown error", e);
                  return null;
              }
          }
          @Override
          protected void onPostExecute(ReturnType res) {
            if(res!=null) {
                Application.getEventBus().post(res);
            } else if(errorResponse!=null) {
                Application.getEventBus().post(errorResponse);
            }
          }
      }.execute();
    }

  }

Your call to your service is now as follows:

    new OurRestService<RecentPosts, RecentPostsServiceInterface>()
        .fetch(recentPostsServiceInterface,
               getResultCallback,
               new ErrorResponseForRecentPosts());

Bonus points if you put the creation of the serviceInterface into the class.

You can also use OkHTTP with retrofit to do transparent response caching and with a bit of work returning things from the cache to provide offline (or before the call) caching.

The code originally appeared in https://github.com/denevell/AndroidQuickstart and a more update to version will soon be in https://github.com/denevell/Natcher.

android android-retrofit android-otto

Page 1 of 1