2
votes

How to create generic class for api calling using RxJava and Retrofit in Kotlin?

My JAVA implementation is as ::

At first Add Gradle Dependency : (update to latest version if available)

//for retrofit

implementation 'com.squareup.retrofit2:retrofit:2.3.0'

implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

//for interceptor implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'

//For BuildType : (if you want for various environment)

buildTypes { debug { buildConfigField "String", "SERVER_URL", '"DEV_URL"' debuggable true } release { buildConfigField "String", "SERVER_URL", '"LIVE_URL"' minifyEnabled false } }

//If environment is set Define BASE_URL using set environment:

String BASE_URL = BuildConfig.SERVER_URL + "url";

=======================================================
Create a class called RestClient For Retrofit Builder :

public class RestClient {

private static RestClient instance;
private ApiConstant apiInterface;
private OkHttpClient.Builder okHttpClientBuilder;

public RestClient() {
    instance = this;
    okHttpClientBuilder = new OkHttpClient.Builder();
    okHttpClientBuilder.connectTimeout(60, TimeUnit.SECONDS);
    okHttpClientBuilder.writeTimeout(60, TimeUnit.SECONDS);
    okHttpClientBuilder.readTimeout(60, TimeUnit.SECONDS);


//for logs of api response in debug mode

    if (BuildConfig.DEBUG) {
        final HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        okHttpClientBuilder.addInterceptor(interceptor);
    }
}

public static RestClient getInstance() {
    return instance;
}

public ApiConstant getApiInterface() {

    final Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
    retrofitBuilder.baseUrl(ApiConstant.BASE_URL);
    retrofitBuilder.client(okHttpClientBuilder.build());
    retrofitBuilder.addConverterFactory(GsonConverterFactory.create());

    Retrofit retrofit = retrofitBuilder.build();
    apiInterface = retrofit.create(ApiConstant.class);

    return apiInterface;
}

}

=======================================================
ApiInterface : (an interface for api calls)

public interface ApiInterface {

@POST("Urls Params")
Call<ModelClass> postValidateToken(@Body JSONObject body);

}

=======================================================
Create Generic class called RetrofitCallback which handles Api response and Throws basic errors and show Toast Message to user:

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

private ProgressDialog progressDialog;
private Context context;
private boolean validateResponse = true;

public RetrofitCallback(Context c) {
    context = c;
}

public RetrofitCallback(Context c, ProgressDialog dialog) {
    progressDialog = dialog;
    context = c;
}

    public RetrofitCallback(Context context, ProgressDialog progressDialog, boolean validateResponse) {
    this.progressDialog = progressDialog;
    this.context = context;
    this.validateResponse = validateResponse;
}

public RetrofitCallback(Context context, boolean validateResponse) {
    this.context = context;
    this.validateResponse = validateResponse;
}

public abstract void onSuccess(T arg0);

@Override
public void onResponse(Call<T> call, Response<T> response) {

    if (!(((Activity) context).isFinishing()) && progressDialog != null && progressDialog.isShowing()) {
        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }

    if (response.isSuccessful() && response.code() == 200) {
        onSuccess(response.body());
    } else {
        Toast.makeText(context, context.getString(R.string.something_wrong), Toast.LENGTH_SHORT).show();
    }
}

@Override
public void onFailure(Call<T> call, Throwable error) {
    if (!validateResponse)
        return;

    String errorMsg;
    error.printStackTrace();
    if (error instanceof SocketTimeoutException) {
        errorMsg = context.getString(R.string.connection_timeout);
    } else if (error instanceof UnknownHostException) {
        errorMsg = context.getString(R.string.nointernet);
    } else if (error instanceof ConnectException) {
        errorMsg = context.getString(R.string.server_not_responding);
    } else if (error instanceof JSONException || error instanceof JsonSyntaxException) {
        errorMsg = context.getString(R.string.parse_error);
    } else if (error instanceof IOException) {
        errorMsg = error.getMessage();
    } else {
        errorMsg = context.getString(R.string.something_wrong);
    }

    if (progressDialog != null && progressDialog.isShowing()) {
        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }
    Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show();
}
}

=======================================================
String : (string.xml)

Something went wrong. Please try again later. No internet connection available. Please try again later. Server is not responding. Please try again later. Connection Timeout Unable to parse response from server

=======================================================
Implementation (How to call in Activity or Fragment):

 Call<User> userLoginCall = RestClient.getInstance().getApiInterface().LoginURL(Util.currentLanguage,
                Util.getLoginURL(getActivity(), email, LOGIN_GOOGLE, accID));
        userLoginCall.enqueue(new RetrofitCallback<User>(getActivity(), DialogUtils.showProgressDialog(getActivity())) {
            @Override
            public void onSuccess(User user) {

                //Your Response
            }
        });
1
What do you mean by 'generic class' and what have you tried so far? - fweigl
I am new in kotlin so I want to simplify my api calling using RxJava By making a one generic class which directly handles errors and give onSuccess directly... - Rushabh Shah
github.com/ApploverSoftware/android_architecture/tree/master/… github.com/ApploverSoftware/android_architecture/blob/master/… I don't have time to explain it now (maybe later if you will need it). My architecture is designed to make it easier to optimise code using rx methods like zip or merge. - Janusz Hain
You can use this adapter library so that you can use RxJava with Retrofit: github.com/square/retrofit/tree/master/retrofit-adapters/… retrofitBuilder.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) - Makotosan

1 Answers

0
votes

I use a ServiceGenerator class just in case I need to use more than one Retrofit Service interfaces.

object ServiceGenerator {

    const val APP_CODE = "APP_CODE"

    private val interceptor = HttpLoggingInterceptor()
        .setLevel(HttpLoggingInterceptor.Level.BODY)

    private val certificatePinner = CertificatePinner.Builder()
        .add(BuildConfig.HOSTNAME, BuildConfig.SHA1_HASH1)
        .add(BuildConfig.HOSTNAME, BuildConfig.SHA1_HASH2)
        .add(BuildConfig.HOSTNAME, BuildConfig.SHA1_HASH3)
        .add(BuildConfig.HOSTNAME, BuildConfig.SHA1_HASH4)
        .build()

    private val httpClient = OkHttpClient.Builder()
        .readTimeout(1, TimeUnit.MINUTES)
        .connectTimeout(1, TimeUnit.MINUTES)
        .addInterceptor(interceptor)

    private val builder = Retrofit.Builder()
        .baseUrl(BuildConfig.URL_ENDPOINT)
        .addCallAdapterFactory(
                    RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())
        )
        .addConverterFactory(GsonConverterFactory.create())

    private var requestInterceptor: AppEventosRequestInterceptor? = null

    fun <S> createService(context: Context, serviceClass: Class<S>): S {
        if (BuildConfig.FLAVOR.equals("production")) {
            httpClient.certificatePinner(certificatePinner)
        }
        if (!httpClient.interceptors().contains(requestInterceptor)) {
            requestInterceptor = AppEventosRequestInterceptor(context)
            httpClient.addInterceptor(requestInterceptor!!)
        }
        builder.client(httpClient.build())
        val retrofit = builder.build()
        return retrofit.create(serviceClass)
    }
}

In my case I made my Service capable of returning a Flowable response so I can observe it.

@GET("schedule")
fun getSchedules(
        @Header("If-None-Match") etag: String
): Flowable<Response<ResponseBody>>

Then, when I want to make my http call I first initialize my ServiceRx class

private var mService: AppEventosServiceRx = ServiceGenerator.createService<AppEventosServiceRx>(applicationContext, AppEventosServiceRx::class.java)

And then make my request

mService.getSchedules(mEtagManager.getEtag(EtagManager.ETAG_SCHEDULES))
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
        { response ->
            if (response.code() != HttpURLConnection.HTTP_NOT_MODIFIED) {
                if (response.body() != null) {
                    // Do your useful stuff here...
                }
            } else {
                // Check if not modified or any other HTTP Error
            }
        },
        { throwable -> 
            // Log your connectivity problems ...
        }
    ) {}

This way, your Service interface will give you a Observer, the Service generator will make it common for every call and you can observe it and work the response.

Hope it helps!