Implementing Retrofit2 service interface

 ̄綄美尐妖づ 提交于 2019-12-04 15:28:13
Marcello Galhardo

In one of my projects I had a similar problem and could not use RxJava. I solved this by using a Command Pattern.

First, I have created a custom Callback class with a clear API. That way I can handle HTTP codes more concisely (such as 400 or 500).

public abstract class RestCallback<T> implements Callback<T> {

    @Override
    public void onResponse(Response<T> response, Retrofit retrofit) {
        if (response != null) {
            if (response.isSuccess()) {
                onSuccess(response, retrofit);
            } else {
                onError(response, retrofit);
            }
        } else {
            onFailure(null);
        }
    }

    abstract public void onSuccess(Response<T> response, Retrofit retrofit);

    abstract public void onError(Response<T> response, Retrofit retrofit);

    abstract public void onFailure(Throwable t);

}

Then I created a Command interface:

public interface Command {

    // New network hit
    void fresh(RestCallback callback);

    // First cached if available then fresh
    void all(RestCallback callback);

    // Cached response only
    void cached(RestCallback callback);

    // If cache exists return it, otherwise return a fresh response
    void get(RestCallback callback);

    // Cancel the request
    void cancel();

}

This way, instead of working directly with Retrofit objects you will implement the Command interface and work with that. For example, you could create a GetCommentsOfPostCommand and let it take care of the Retrofit Call object, encapsulating request logic.

This way you can work as you like; implementing cache, requests, cancellations, etc. while exposing a well-defined interface.

Hope this helps.

If I'm understanding your question right, you'd like to fetch data from your SQLite just in case there's no network connection.

You have this interface, where you define your endpoint:

public interface CommentsService {  
    @GET("posts/{postId}/comments")
    Call<List<Comment>> getCommentsOfPost(@Path("postId") int id);
}

Then you should have another interface, which you use from another part of your application:

public interface GetCommentsService {
    void getComments(int id, CommentsServiceResultListener listener);
}

Where CommentsServiceResultListener is the listener you're passing to the implementation of the previous interface. Let's define it as follows:

public interface CommentsServiceResultListener {

    void onResponse(List<Comment> response);

    void onError(String errorMessage);
}

The, you need to implement your GetCommensService interface, in order to actually get the data. You can do it like:

public class GetCommensServiceImpl implemens GetCommensService {
    private static final String TAG = BuildingsBaseServiceImpl.class.getSimpleName();

    @Override
    public void getComments(int id, CommentsServiceResultListener listener) {
        CommentsService service = getService();
        Call<List<Comment>> request = service.getCommentsOfPost(id);
        request.enqueue(new Callback<List<Comment>>(){

            @Override  //if this method is executed, the actual call has been made
            public void onResponse(Call<List<Comment>> call, Response<List<Comment>> response) { 
                if (response.isSuccessful()) {
                    listener.onResponse(response.body());
                } else {
                    //TODO check here if the call wans't successful because a network problem. In that case, fetch from your SQLite
                    //Default unsuccessful call management
                    Log.e(TAG, response.code() + ": " + response.message());
                    listener.onError(response.message());
                }
            }

            @Override //maybe the call couldn't be made because of lack of connection. 
            public void onFailure(Call<List<Comment>> call, Throwable t) {
                //TODO check the failure cause, then decide if there's need to fetch from your SQLite.
                //Default failure management
                Log.e(TAG, t.getMessage() + "", t);
                listener.onError(t.getMessage() + "");
            }
        });
    }

    private CommentsService getService() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://your.base.url/")
                .addConverterFactory(GsonConverterFactory.create()) //assuming JSON
                .build();
        return retrofit.create(CommentsService.class);
    }
}

Hope this helps!!

In retrofit2, you can use Call.enqueue(Callback) to specify the callback. For example,

getCommentsService.getCommentsOfPost(myInt).enqueue(new Callback() {
    @Override
    public void onResponse(Call call, Response response) {
        // do something with the response
    }
}

If you want a synchronous execution, use Call.execute().

If you have experience with RxJava, It'll become easier because you can combine multiple task while getting data, and show it as quickly as possible. So, the first thing you have to do is save the api result into your database. The next time you want to show the data, you can call concat method to concatenate multiple source request (from cache, disk, or server) to retrieves data as quickly as possible.

Here is the good explanation about that.

http://blog.danlew.net/2015/06/22/loading-data-from-multiple-sources-with-rxjava/ https://medium.com/@murki/chaining-multiple-sources-with-rxjava-20eb6850e5d9#.2u8zssice

Since you only want to check the local database when network isn't available, what you could also is implement a callback interface and pass it as a parameter to the function which is making the retrofit call. This way, you can make your function return void, and from inside the onResonse or onFailure method of your retrofit call, you can decide whether to load from the successful response(onResponse) or from local database(onFailure), i.e., make the required call to the required function of your custom interface. This will mimic the synchronous behavior of your retrofit call in an asynchronous call, while giving you the flexibility to make decisions even when you receive improper response.

So ok , lets imagine that Retrofit also provides mechanism (for ex. by passing special callback if error - load from DB). It would be very specific, because:

  • If I want to update token, for security data
  • If I want to recheck connection manually
  • If I want show special data
  • ...

Primary task of using Retrofit it's getting result from connection, and status of work. And that is all.

Also answering your question. You may provide your own behavior, for case where is no net connection, and call onDataBaseLoadIfError(). And than call in your overridden Retrofit class - Call.

public interface CommentsServiceListener {

    void onResponse(List<Comment> response);

    void onError(String errorMessage);

    void onDataBaseLoadIfError(String errorMessage);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!