5

I am using Retrofit to interact with my REST API, and was wondering whether anybody has any design suggestions.

My app has the following packages:

  1. models
  2. services
  3. activities
  4. fragments

The services package contains the interfaces for Retrofit. For example:

public interface FooService {
    @FormUrlEncoded
    @POST("foo/do")
    @Headers("Content-Type: application/x-www-form-urlencoded; charset=UTF-8")
    Call<FooBar> do();
}

Models contains...well, the different models. For example, FooBar. So far so good - just as per Retrofit documentation.

I have created an API class, that handles the Retrofit build logic (Retrofit retrofit = new Retrofit.Builder() etc), and exposes a static field: retrofit.In my activities I then can perform my requests as follows:

FooService service = API.retrofit.create(FooService.class);
Call<FooBar> call = service.do();

try {
    retrofit2.Response response = call.execute();
    // ...do stuff...
} catch(IOException) {}

And herewith comes my question: Would it be better to abstract the above further? So that I would not need to repeat the above everywhere? For example, something like:

MyOtherFooService service = new  MyOtherFooService();
FooBar fooBar = service.do();

Any thoughts? Recommendations?

JB2
  • 1,587
  • 2
  • 24
  • 40

2 Answers2

13

I usually use singleton pattern with following structure :

first define ServiceHelper like following :

public class ServiceHelper {

private static final String ENDPOINT = "http://test.com";

private static OkHttpClient httpClient = new OkHttpClient();
private static ServiceHelper instance = new ServiceHelper();
private IPlusService service;


private ServiceHelper() {

    Retrofit retrofit = createAdapter().build();
    service = retrofit.create(IPlusService.class);
}

public static ServiceHelper getInstance() {
    return instance;
}

private Retrofit.Builder createAdapter() {

    httpClient.setReadTimeout(60, TimeUnit.SECONDS);
    httpClient.setConnectTimeout(60, TimeUnit.SECONDS);
    HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
    interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
    httpClient.interceptors().add(interceptor);

    return new Retrofit.Builder()
            .baseUrl(ENDPOINT)
            .client(httpClient)
            .addConverterFactory(GsonConverterFactory.create());
}

public Call<List<CategoryModel>> getAllCategory() {
    return service.getAllCategory();
}

Then put all of your services in IService (in my case it's IPlusService)

    public interface IPlusService {
    //@Headers( "Content-Type: application/json" ) in Post method may use this
    @GET("/api/category")
    Call<List<CategoryModel>> getAllCategory();
}

Then call your singleton like below in your activity/fragment :

ServiceHelper.getInstance().getAllCategory().enqueue(new Callback<List<CategoryModel>>() {
        @Override
        public void onResponse(Response<List<CategoryModel>> response, Retrofit retrofit) {
            processResponse(response);
        }

        @Override
        public void onFailure(Throwable t) {
            processResponse(null);
        }
    });
Amir
  • 16,067
  • 10
  • 80
  • 119
  • This code taken from my previous project and it use retrofit version : 'retrofit:2.0.0-beta2' and I think you should change it a bit. – Amir May 01 '16 at 04:49
  • This is my approach as well Amir, as documented in the Retrofit book. https://leanpub.com/retrofit-love-working-with-apis-on-android . I like the fact you didn't add anything else to it. KISS :) – Radu May 01 '16 at 07:18
  • @Radu really? :D I used this approach from 2years ago. I learn this from one of my co-worker. BTW Thanks for you great approach ;) – Amir May 01 '16 at 07:22
  • 1
    @Amir nice code snippet! I extended your patter a bit. – Randika Vishman Jun 01 '16 at 17:04
  • 1
    And now my next experiment is towards this library *RoboSpice* https://github.com/stephanenicolas/robospice – Randika Vishman Jun 01 '16 at 17:21
  • This is an **untestable** solution, for your attention. – Eido95 Sep 06 '18 at 06:44
1

I would say don't do it. Especialy because most calls will need to use the async .enqueue and treat the error responses differently.

You could make your own abstract Retrofit Callback class and abstract the 'custom' callbacks from that so that you save yourself code duplication.

But generally I would like to see the app logic treating the response in the same place where I make the request.

Don't fall in the trap of overthinking it, or applying some abused pattern that doesn't really fit into Android.

And if your really need to pass that response along from your activity/fragment just use RxJava or even LocalBroadcast. Also try to read the Retrofit book.

Radu
  • 2,076
  • 2
  • 20
  • 40
  • 1
    @Amir The book is great. I recommend everyone buys it. Even though the same content of the book is available for free in a blog, the book is the best option. I suggest to the guy who asked the question that he does not start with a pattern. Instead he should just use common sense and use async calls in his activities/fragments. When the results need to be broadcast I use LocalBroadcast or RxJ – Radu May 01 '16 at 09:11
  • Thanks Radu, Amir. On a somewhat related note: Would you recommend creating a separate package for all Async tasks to be used here? Or keep them as inner classes? – JB2 May 01 '16 at 23:55
  • 1
    @JB2 No Async tasks! At all! You have the enqueue method for asynchronous behavior! – Radu May 02 '16 at 06:51
  • @Radu: Cool! Very nice! :D Thanks. – JB2 May 02 '16 at 20:17