Andrew
Andrew

Reputation: 53

Handle Retrofit Callback using Kotlin

Maybe this is a very easy topic but I cannot find the correct answer.

This is the deal, I was using this logic for handle API's into my current android project:

Now that I'm moving to Kotlin, I cannot replicate the enqueue() function from retrofit into my Api class. This is an example of my code:

GetUserData.java:

class GetUserData {
private void tryGetData() {
    sampleApi.getSomeData(userId, new Callback<Data>() {
        @Override
        public void onResponse(final Call<Data> call, final Response<Data> response) {
            ......Do Something
        }

        @Override
        public void onFailure(final Call<Data> call, final Throwable t) {
            ......Do Something
        }
    });

I'm using a interface in order to declare the methods to be implemented into the API class:

public interface ISampleApi {

void getSomeData(@NonNull String userId,
                     @NonNull Callback<Data> callback);

}

This is my API class:

public class SampleApi implements ISampleApi {
............
  @Override
  public void getSomeData(@NonNull final String userId,
                            @NonNull final Callback<Data> callback) {
    myClient.getSomeData(userId).enqueue(callback);
  }
}

And this is MyClient using Retrofit:

public interface SampleClient {
  @GET("user/data/{userId}")
  Call<Data> getSomeData(@NonNull @Path("userId") String userId);
}

All the code above works fine, that's what I'm using in my app without problem.

Now, this is the problem when move to Kotlin, I'm try to create the enqueue callback into SampleApi and I need to return the response to the class where the callback was created, in Kotlin should be something like this:

fun getSomeData(userId: String, callback: CallBack<Data>) {
  client?.getSomeData(userId).enqueue(callback)
}

But when try to do that, Android Studio tell me that I have to create a extension function, I create the extension function, and after more suggestion from Android Studio and some research the result is something like this:

override fun getSomeData(@NonNull userId: String,
                             @NonNull callback: Callback<Data>) {//Neve using this callback
    client?.getSomeData(userId).enqueue {
      success = {
       success -> Do Something
      }
      failure = {
        failure -> Do Something
      }
    }
}

fun<T> Call<T>.enqueue(callback: BaseCallback<T>.() -> Unit) {
    val callbackBk = BaseCallback<T>()
    callback.invoke(callbackBk)
    this.enqueue(callbackBk)

}

class BaseCallback<T>: Callback<T> {
    var success: ((Response<T>) -> Unit)? = null
    var failure: ((t: Throwable?) -> Unit)? = null

    override fun onFailure(call: Call<T>, t: Throwable) {
        failure?.invoke(t)
    }

    override fun onResponse(call: Call<T>, response: Response<T>) {
        success?.invoke(response)
    }
}

BaseCallback.kt

open class BaseCallback<T: Validatable>(): Callback<BaseResponseBody<T>> {
var success: ((T) -> Unit)? = null
var failure: ((networkingError: NetworkingError?) -> Unit)? = null
override fun onResponse(call: Call<BaseResponseBody<T>>, response: Response<BaseResponseBody<T>>) {

    if (response?.code() != null
            && response.isSuccessful
            && response.body() != null
            && response.body().containsValidSuccess()) run {

        onSuccessfulResponse(response.body().data!!)
    } else if (response != null
            && response.isSuccessful
            && response.body() != null) {

        onSuccessfulResponse()
    } else {
        onNonSessionErrorEncountered(networkingError)
    }
}

override fun onFailure(call: Call<BaseResponseBody<T>>, throwable: Throwable) {
    onNonSessionErrorEncountered(NetworkStateError(throwable))
}

@VisibleForTesting(otherwise = PROTECTED)
fun onSuccessfulResponse(responseValue: T) {
    success?.invoke(responseValue)
}

@VisibleForTesting(otherwise = PROTECTED)
fun onSuccessfulResponse() {
    // This method intentionally left blank.
}

fun onSessionErrorEncountered() {
    // This method intentionally left blank.
}

@VisibleForTesting(otherwise = PROTECTED)
fun onNonSessionErrorEncountered(networkingError: NetworkingError) {
    failure?.invoke(networkingError)
}
}

This code works fine here, I can receive the data, but I need to receive the response inside the class that call this endpoint, since I need to change the UI.

I really appreciate a lot some help, I have been stuck with this for a while and I need to solve the issue to move on with the app.

Upvotes: 2

Views: 11844

Answers (1)

Vikash Bijarniya
Vikash Bijarniya

Reputation: 424

Put below code in AppRepository.kt file

companion object { 

fun getServiceDetails(map: Map<String, String>, callback: ApiCallback<ServiceDetailsDataClass.ServiceDetailsResponse>) {
        val retrofit = ApiClient.retrofit
        val callGet = retrofit?.create<ApiService.ServiceDetails>(ApiService.ServiceDetails::class.java)?.getServiceDetails(map, ApiConstants.API_KEY, ApiConstants.API_LANG)

        callGet?.enqueue(object : Callback<ServiceDetailsDataClass.ServiceDetailsResponse> {
            override fun onFailure(call: Call<ServiceDetailsDataClass.ServiceDetailsResponse>?, t: Throwable?) {
                callback.onError(t.toString())
            }

            override fun onResponse(call: Call<ServiceDetailsDataClass.ServiceDetailsResponse>?, response: Response<ServiceDetailsDataClass.ServiceDetailsResponse>?) {
                if (response?.isSuccessful!!) {
                    callback.onSuccess(response.body()!!)
                } else {
                    setError(callback, response)
                }
            }

        })
    }

ApiCallBack.kt interface file has the code:

interface ApiCallback<T> {
fun onException(error: Throwable)

fun onError(error: String)

fun onSuccess(t: T)

}

ApiService.kt file has the code:

 interface ApiService {

interface ServiceDetails {
    @GET("api/path/here/")
    fun getServiceDetails(@QueryMap map: Map<String, String>, @Header(ApiConstants.KEY_X_API) apiKey: String, @Header(ApiConstants.KEY_ACCEPT_LANGUAGE) language: String): Call<ServiceDetailsDataClass.ServiceDetailsResponse>
}

Now call api using :

AppRepository.getServiceDetails(map, object : ApiCallback<ServiceDetailsDataClass.ServiceDetailsResponse> {
        override fun onException(error: Throwable) {
            serviceDetailsResponse.value = error
        }

        override fun onError(error: String) {
            serviceDetailsResponse.value = error
        }

        override fun onSuccess(t: ServiceDetailsDataClass.ServiceDetailsResponse) {
            serviceDetailsResponse.value = t

        }

    })

Hope it helps you.

Upvotes: 3

Related Questions