3

I am trying to follow the REST client implementation pattern described in the Google I/O Dobjanschi video here and am using Retrofit2 for the REST API calls.

Based on the REST client pattern described above I introduced a ServiceHelper layer that calls the actual API method via Retrofit. However I don't have a clean way to call the interface methods from the ServiceHelper layer.

I currently have an enum of the available API calls and pass that from the ServiceHelper. And in my ApiProcessor introduced a function that uses an giant if..else if ladder that returns the appropriate Retrofit API interface call based on the enum passed in. I haven't really found a better/cleaner approach to this.

Is there a better / cleaner way to map these? Or any other ideas to do this?

Bootstrapper
  • 1,089
  • 3
  • 14
  • 33

3 Answers3

1

You should throw away that monolithic ServiceHelper and create several repositories following the repository pattern in order to encapsulate and distribute responsibilities between classes.

Actually, the Retrofit API itself favors composition over inheritance, so you can easily create as much interfaces as needed and use them in the right repository.

Víctor Albertos
  • 8,093
  • 5
  • 43
  • 71
  • hmm.. wouldn't that mean you can't do things like caching in a single place and basically recreating the HTTP client (and the associated setup) for each request? which is repeating yourself each time? – Bootstrapper Jul 19 '16 at 06:58
  • Not at all. You should use dependency inversion to inject retrofit instances in each repository, that way the initialization is done in one place. You can use Dagger to alleviate the pain of creating manually every single dependency. – Víctor Albertos Jul 19 '16 at 08:27
  • btw the link you added above seems to suggest Repository pattern is an over abstraction. So a little confused? – Bootstrapper Jul 19 '16 at 08:35
  • Have you read the post till the end? One thing is creating a single interface to abstract the repository, one different thing is to work with the concrete implementation of the repository without using an interface as a supplementary indirection layer. So, one way or another, you are going to need this concept represented in your code, the way you do it it's up to you. Personally, I don't like at all fatuous abstractions. – Víctor Albertos Jul 19 '16 at 08:39
0

Without the code it is a bit hard to "inspect" your solution. :)

As You asked the question it is not really the best way to solve the problem in this way (in my opinion). Altough there are a ton approaches like "if it works it is OK".

In my opinion a bit cleaner solution would be the following: Your helper is a good thing. It should be used to hide all the details of the API You are using. It is a good thing to hide those API specific stuff because if it changes You only forced to change only your helper/adapter. My recommendation is to use multiple method in apiprocessor and not enums. It is a bit easier to maintain and fix if something is changing. Plus you do not have to take care of your Enum.

TLDR: If it works probably it is OK. You do not have to write million dollar production code to test something, but if you would like to get a good habit You should consider the refactor that code into separate processor methods.

Hash
  • 4,647
  • 5
  • 21
  • 39
0

You Can follow service pattern:

a) Create resource interface which are your exposed rest methods eg:

public interface AppResource {

    @Headers({"Accept: application/json", "Content-Type: application/json"})
    @GET(ApiConstants.API_VERSION_V1 + "/users")
    Call<List<User>> getUsers();

}

b) Create RetrofitFactory

 public class RetrofitFactory {

    private static Retrofit userRetrofit;


        @NonNull
            private static Retrofit initRetrofit(String serverUrl) {


     final HttpLoggingInterceptor logging = new HttpLoggingInterceptor();

            // set your desired log level
            logging.setLevel(HttpLoggingInterceptor.Level.BODY);
            final OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(10, TimeUnit.SECONDS)
                    .readTimeout(30, TimeUnit.SECONDS)
                    .addInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
                            final Request original = chain.request();
                            final Request request = original.newBuilder()
                                                                    .method(original.method(), original.body())
                                    .build();

                            return chain.proceed(request);
                        }
                    })
                    .addInterceptor(logging)
                    .build();
            return new Retrofit.Builder()
                    .baseUrl(serverUrl)
                    .addConverterFactory(JacksonConverterFactory.create())
                    .client(okHttpClient)
                    .build();
        }

       public static Retrofit getUserRetrofit() {
        if (userRetrofit == null) {
                       final String serverUrl = context.getString(R.string.server_url); //Get context

            userRetrofit = initRetrofit(serverUrl);
        }
        return userRetrofit;
    }
 }

c) Create a abstract BaseService which your every service will extend

public abstract class BaseService<Resource> {


    protected final Resource resource;

    final Retrofit retrofit;

    public BaseService(Class<Resource> clazz) {
        this(clazz, false);
    }

    public BaseService(Class<Resource> clazz) {

            retrofit = RetrofitFactory.getUserRetrofit();

        resource = retrofit.create(clazz);
    }

    protected <T> void handleResponse(Call<T> call, final ResponseHandler<T> responseHandler) {
        call.enqueue(new Callback<T>() {
            @Override
            public void onResponse(final Call<T> call, final Response<T> response) {
                if (response.isSuccess()) {
                    if (responseHandler != null) {
                        responseHandler.onResponse(response.body());
                    }
                } else {
                    final ErrorResponse errorResponse = parseError(response);
                    if (responseHandler != null) {
                        responseHandler.onError(errorResponse);
                    }
                }
            }

            @Override
            public void onFailure(final Call<T> call, final Throwable throwable) {
                if (responseHandler != null) {
                    responseHandler.onFailure(throwable);
                }
            }
        });
    }

} 

d) Now your user service with their response handler

public interface UserService {

   void getUsers(ResponseHandler<List<User>> userListResponse);
}

e) Now your user service implementation class which extends baseservice

public class UserServiceImpl extends BaseService<UserResource> implements UserService {
    public UserServiceImpl () {
        super(AppResource.class);
    }

    @Override
    public void getUsers(ResponseHandler<List<User>> userListResponse) throws UserServiceException {
        final Call<List<User>> response = resource.getUsers();
        handleResponse(response, userListResponse);
    }

f) Create a service factory which you will reuse to call services eg:

public class ServiceFactory {

private static UserService userservice;

UserService  getUserService(){
if (UserService == null) {
            UserService = new UserServiceImpl();
        }
        return UserService ;
}   

g) Now simply call service and pass your response handler

ServiceFactory.getUserService().getUsers(getUserListResponseHandler());
    } catch (your exception handler) {
       //log your excp
     }
Rahul Teotia
  • 184
  • 1
  • 5