Skip to content

Latest commit

 

History

History
281 lines (222 loc) · 10.5 KB

android-retrofit-otto.md

File metadata and controls

281 lines (222 loc) · 10.5 KB

title: Android: Retrofit and Otto tags: android, android-retrofit, android-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.