مثالی برای کار با کتابخانه Retrofit


کتابخانه Retrofit

پیاده سازی تلاش مجدد برای ارتباط با سرور در کتابخانه Retrofit

کار با کتابخانه Retrofit در اندروید موضوع یکی از مقالات پیشین بلاگ بود. دلایل زیادی برای عدم موفقیت یک Request در برنامه وجود دارد. از جمله اینترنت ضعیف، پهنای باند کم و مواردی از این قبیل. پس ضروری ست برای برنامه ای که نیاز به ایجاد request یا درخواستی سمت سرور دارد، امکانی برای درخواست مجدد به سرور به صورت خودکار ایجاد شود. چنین امکانی تجربه کاربری بهتری را موجب می شود. در این مقاله برای کار با کتابخانه Retrofit امکان ایجاد request مجدد در صورت عدم موفقیت یک request پیاده می سازیم.

قدم اول : ساخت پروژه

در اندروید استودیو پروژه جدیدی را ایجاد می کنیم. و کتابخانه Retrofit و کتابخانه Gson را به پروژه اضافه می نماییم :

implementation 'com.squareup.retrofit2:retrofit:2.7.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'

قدم دوم : ایجاد annotation برای تلاش مجدد جهت گرفتن پاسخ از سرور

به این منظور اینترفیسی با حاشیه نگاری Retry annotation می سازیم. در بین این موارد سه مورد وجود دارد که بنا به نیاز پروژه متغیر می باشند.

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface Retry {
  int max() default 3;
}

قدم سوم : ساخت کلاس RetryCallAdapterFactory

در کتابخانه Retrofit کلاسی با نام CallAdapter.Factory وجود دارد. این کلاس با دیزاین پترن Factory پیاده شده است. مسئولیت آن چک نمودن درخواست یا request است که آیا با حاشیه نگاری @Retry مشخص گردیده یا خیر. در صورت وجود @Retry هر request اگر با موفقیت پاسخ داده نشود دست کم سه بار تکرار می شود.

import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import okhttp3.Request;
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class RetryCallAdapterFactory extends CallAdapter.Factory {
  private static final String TAG = "RetryCallAdapterFactory";
  public static RetryCallAdapterFactory create() {
    return new RetryCallAdapterFactory();
  }
  @Nullable
  @Override
  public CallAdapter<?, ?> get(@NonNull Type returnType, @NonNull Annotation[] annotations,
      @NonNull Retrofit retrofit) {
    /**
     * You can setup a default max retry count for all connections.
     */
    int itShouldRetry = 0;
    final Retry retry = getRetry(annotations);
    if (retry != null) {
      itShouldRetry = retry.max();
    }
    Log.d(TAG, "Starting a CallAdapter with {} retries." + itShouldRetry);
    return new RetryCallAdapter<>(
        retrofit.nextCallAdapter(this, returnType, annotations),
        itShouldRetry
    );
  }
  private Retry getRetry(@NonNull Annotation[] annotations) {
    for (Annotation annotation : annotations) {
      if (annotation instanceof Retry) {
        return (Retry) annotation;
      }
    }
    return null;
  }
  static final class RetryCallAdapter<R, T> implements CallAdapter<R, T> {
    private final CallAdapter<R, T> delegated;
    private final int maxRetries;
    public RetryCallAdapter(CallAdapter<R, T> delegated, int maxRetries) {
      this.delegated = delegated;
      this.maxRetries = maxRetries;
    }
    @Override
    public Type responseType() {
      return delegated.responseType();
    }
    @Override
    public T adapt(final Call<R> call) {
      return delegated.adapt(maxRetries > 0 ? new RetryingCall<>(call, maxRetries) : call);
    }
  }
  static final class RetryingCall<R> implements Call<R> {
    private final Call<R> delegated;
    private final int maxRetries;
    public RetryingCall(Call<R> delegated, int maxRetries) {
      this.delegated = delegated;
      this.maxRetries = maxRetries;
    }
    @Override
    public Response<R> execute() throws IOException {
      return delegated.execute();
    }
    @Override
    public void enqueue(@NonNull Callback<R> callback) {
      delegated.enqueue(new RetryCallback<>(delegated, callback, maxRetries));
    }
    @Override
    public boolean isExecuted() {
      return delegated.isExecuted();
    }
    @Override
    public void cancel() {
      delegated.cancel();
    }
    @Override
    public boolean isCanceled() {
      return delegated.isCanceled();
    }
    @Override
    public Call<R> clone() {
      return new RetryingCall<>(delegated.clone(), maxRetries);
    }
    @Override
    public Request request() {
      return delegated.request();
    }
  }
  static final class RetryCallback<T> implements Callback<T> {
    private final Call<T> call;
    private final Callback<T> callback;
    private final int maxRetries;
    public RetryCallback(Call<T> call, Callback<T> callback, int maxRetries) {
      this.call = call;
      this.callback = callback;
      this.maxRetries = maxRetries;
    }
    private final AtomicInteger retryCount = new AtomicInteger(0);
    @Override
    public void onResponse(@NonNull Call<T> call, @NonNull Response<T> response) {
      if (!response.isSuccessful() && retryCount.incrementAndGet() <= maxRetries) {
        Log.d(TAG, "Call with no success result code: {} " + response.code());
        retryCall();
      } else {
        callback.onResponse(call, response);
      }
    }
    @Override
    public void onFailure(@NonNull Call<T> call, @NonNull Throwable t) {
      Log.d(TAG, "Call failed with message:  " + t.getMessage(), t);
      if (retryCount.incrementAndGet() <= maxRetries) {
        retryCall();
      } else if (maxRetries > 0) {
        Log.d(TAG, "No retries left sending timeout up.");
        callback.onFailure(call,
            new TimeoutException(String.format("No retries left after %s attempts.", maxRetries)));
      } else {
        callback.onFailure(call, t);
      }
    }
    private void retryCall() {
      Log.w(TAG, "" + retryCount.get() + "/" + maxRetries + " " + " Retrying...");
      call.clone().enqueue(this);
    }
  }
}

قدم چهارم : ساخت کلاس entity برای پاسخ سرور

برای تبدیل جواب سرور به object ی که در برنامه قابل استفاده باشد، نیاز به ساخت چنین کلاسی داریم.

import com.google.gson.annotations.SerializedName;
public class UserResponse {
  @SerializedName("authToken")
  private String authToken;
  @SerializedName("data")
  private Object data;
  @SerializedName("error")
  private Boolean error;
  @SerializedName("message")
  private String message;
  @SerializedName("statusCode")
  private Long statusCode;
  public String getAuthToken() {
    return authToken;
  }
  public Object getData() {
    return data;
  }
  public Boolean getError() {
    return error;
  }
  public String getMessage() {
    return message;
  }
  public Long getStatusCode() {
    return statusCode;
  }
}

قدم پنجم : ساخت یک interface برای تعامل با کتابخانه Retrofit

import com.retrofitautoretryexample.model.UserResponse;
import com.retrofitautoretryexample.retrofit.Retry;
import retrofit2.Call;
import retrofit2.http.GET;
public interface UserApiService {
  @Retry
  @GET("user")
  Call<UserResponse> getUsers();
}

قدم ششم : ساخت کلاسی برای ارتباط با سرور

در این کلاس نمونه ای از کتابخانه Retrofit می سازیم که کلاس UserApiService  را به عنوان مقدار بازگشتی می دهد.

import com.retrofitautoretryexample.retrofit.RetryCallAdapterFactory;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitClient {
  private static final String BASE_URL = "https://jsonplaceholder.typicode.com";
  private static Retrofit retrofit = null;
  public static UserApiService getApiService() {
    if (retrofit == null) {
      retrofit = new Retrofit
          .Builder()
          .baseUrl(BASE_URL)
          .addCallAdapterFactory(RetryCallAdapterFactory.create())
          .addConverterFactory(GsonConverterFactory.create())
          .build();
    }
    return retrofit.create(UserApiService.class);
  }
}

قدم آخر : تکمیل کلاس MainActivity

در این کلاس API مورد نظر صدا زده می شود و نتیجه نهایی در TextView نمایش داده می شود. به این منظور قطعه کد زیر را در MainActivity پیاده می سازیم :

import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.retrofitautoretryexample.model.UserResponse;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainActivity extends AppCompatActivity {
  private TextView txvResult;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    txvResult = findViewById(R.id.txvResults);
    UserApiService apiService = RetrofitClient.getApiService();
    apiService.getUsers().enqueue(new Callback<UserResponse>() {
      @Override public void onResponse(Call<UserResponse> call, Response<UserResponse> response) {
        txvResult.setText(response.body().getMessage());
        Toast.makeText(getApplicationContext(), "Success " + response.body().getMessage(),
            Toast.LENGTH_LONG).show();
      }
      @Override public void onFailure(Call<UserResponse> call, Throwable t) {
        txvResult.setText(t.getMessage());
        Toast.makeText(getApplicationContext(), "Failure " + t.getMessage(), Toast.LENGTH_LONG)
            .show();
      }
    });
  }
}

مثالی برای کار با کتابخانه Retrofit | پیاده سازی تلاش مجدد برای یک request

به این پست امتیاز دهید

روی ستاره های کلیک کنید و امتیاز بدید

میانگین امتیاز 0 / 5. تعداد: 0

از امتیاز شما متشکریم

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

Enter Captcha Here : *

Reload Image