How to create a call adapter for suspending functions in Retrofit? - java

I need to create a retrofit call adapter which can handle such network calls:
#GET("user")
suspend fun getUser(): MyResponseWrapper<User>
I want it to work with Kotlin Coroutines without using Deferred. I have already have a successful implementation using Deferred, which can handle methods such as:
#GET("user")
fun getUser(): Deferred<MyResponseWrapper<User>>
But I want the ability make the function a suspending function and remove the Deferred wrapper.
With suspending functions, Retrofit works as if there is a Call wrapper around the return type, so suspend fun getUser(): User is treated as fun getUser(): Call<User>
My Implementation
I have tried to create a call adapter which tries to handle this. Here is my implementation so far:
Factory
class MyWrapperAdapterFactory : CallAdapter.Factory() {
override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
val rawType = getRawType(returnType)
if (rawType == Call::class.java) {
returnType as? ParameterizedType
?: throw IllegalStateException("$returnType must be parameterized")
val containerType = getParameterUpperBound(0, returnType)
if (getRawType(containerType) != MyWrapper::class.java) {
return null
}
containerType as? ParameterizedType
?: throw IllegalStateException("MyWrapper must be parameterized")
val successBodyType = getParameterUpperBound(0, containerType)
val errorBodyType = getParameterUpperBound(1, containerType)
val errorBodyConverter = retrofit.nextResponseBodyConverter<Any>(
null,
errorBodyType,
annotations
)
return MyWrapperAdapter<Any, Any>(successBodyType, errorBodyConverter)
}
return null
}
Adapter
class MyWrapperAdapter<T : Any>(
private val successBodyType: Type
) : CallAdapter<T, MyWrapper<T>> {
override fun adapt(call: Call<T>): MyWrapper<T> {
return try {
call.execute().toMyWrapper<T>()
} catch (e: IOException) {
e.toNetworkErrorWrapper()
}
}
override fun responseType(): Type = successBodyType
}
runBlocking {
val user: MyWrapper<User> = service.getUser()
}
Everything works as expected using this implementation, but just before the result of the network call is delivered to the user variable, I get the following error:
java.lang.ClassCastException: com.myproject.MyWrapper cannot be cast to retrofit2.Call
at retrofit2.HttpServiceMethod$SuspendForBody.adapt(HttpServiceMethod.java:185)
at retrofit2.HttpServiceMethod.invoke(HttpServiceMethod.java:132)
at retrofit2.Retrofit$1.invoke(Retrofit.java:149)
at com.sun.proxy.$Proxy6.getText(Unknown Source)
...
From Retrofit's source, here is the piece of code at HttpServiceMethod.java:185:
#Override protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call); // ERROR OCCURS HERE
//noinspection unchecked Checked by reflection inside RequestFactory.
Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
return isNullable
? KotlinExtensions.awaitNullable(call, continuation)
: KotlinExtensions.await(call, continuation);
}
I'm not sure how to handle this error. Is there a way to fix?

Here is a working example of an adapter, which automatically wraps a response to the Result wrapper. A GitHub sample is also available.
// build.gradle
...
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
implementation 'com.google.code.gson:gson:2.8.5'
}
// test.kt
...
sealed class Result<out T> {
data class Success<T>(val data: T?) : Result<T>()
data class Failure(val statusCode: Int?) : Result<Nothing>()
object NetworkError : Result<Nothing>()
}
data class Bar(
#SerializedName("foo")
val foo: String
)
interface Service {
#GET("bar")
suspend fun getBar(): Result<Bar>
#GET("bars")
suspend fun getBars(): Result<List<Bar>>
}
abstract class CallDelegate<TIn, TOut>(
protected val proxy: Call<TIn>
) : Call<TOut> {
override fun execute(): Response<TOut> = throw NotImplementedError()
override final fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)
override final fun clone(): Call<TOut> = cloneImpl()
override fun cancel() = proxy.cancel()
override fun request(): Request = proxy.request()
override fun isExecuted() = proxy.isExecuted
override fun isCanceled() = proxy.isCanceled
abstract fun enqueueImpl(callback: Callback<TOut>)
abstract fun cloneImpl(): Call<TOut>
}
class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, Result<T>>(proxy) {
override fun enqueueImpl(callback: Callback<Result<T>>) = proxy.enqueue(object: Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val code = response.code()
val result = if (code in 200 until 300) {
val body = response.body()
Result.Success(body)
} else {
Result.Failure(code)
}
callback.onResponse(this#ResultCall, Response.success(result))
}
override fun onFailure(call: Call<T>, t: Throwable) {
val result = if (t is IOException) {
Result.NetworkError
} else {
Result.Failure(null)
}
callback.onResponse(this#ResultCall, Response.success(result))
}
})
override fun cloneImpl() = ResultCall(proxy.clone())
}
class ResultAdapter(
private val type: Type
): CallAdapter<Type, Call<Result<Type>>> {
override fun responseType() = type
override fun adapt(call: Call<Type>): Call<Result<Type>> = ResultCall(call)
}
class MyCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
) = when (getRawType(returnType)) {
Call::class.java -> {
val callType = getParameterUpperBound(0, returnType as ParameterizedType)
when (getRawType(callType)) {
Result::class.java -> {
val resultType = getParameterUpperBound(0, callType as ParameterizedType)
ResultAdapter(resultType)
}
else -> null
}
}
else -> null
}
}
/**
* A Mock interceptor that returns a test data
*/
class MockInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val response = when (chain.request().url().encodedPath()) {
"/bar" -> """{"foo":"baz"}"""
"/bars" -> """[{"foo":"baz1"},{"foo":"baz2"}]"""
else -> throw Error("unknown request")
}
val mediaType = MediaType.parse("application/json")
val responseBody = ResponseBody.create(mediaType, response)
return okhttp3.Response.Builder()
.protocol(Protocol.HTTP_1_0)
.request(chain.request())
.code(200)
.message("")
.body(responseBody)
.build()
}
}
suspend fun test() {
val mockInterceptor = MockInterceptor()
val mockClient = OkHttpClient.Builder()
.addInterceptor(mockInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://mock.com/")
.client(mockClient)
.addCallAdapterFactory(MyCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(Service::class.java)
val bar = service.getBar()
val bars = service.getBars()
...
}
...

When you use Retrofit 2.6.0 with coroutines you don't need a wrapper anymore. It should look like below:
#GET("user")
suspend fun getUser(): User
You don't need MyResponseWrapper anymore, and when you call it, it should look like
runBlocking {
val user: User = service.getUser()
}
To get the retrofit Response you can do the following:
#GET("user")
suspend fun getUser(): Response<User>
You also don't need the MyWrapperAdapterFactory or the MyWrapperAdapter.
Hope this answered your question!
Edit
CommonsWare# has also mentioned this in the comments above
Edit
Handling error could be as follow:
sealed class ApiResponse<T> {
companion object {
fun <T> create(response: Response<T>): ApiResponse<T> {
return if(response.isSuccessful) {
val body = response.body()
// Empty body
if (body == null || response.code() == 204) {
ApiSuccessEmptyResponse()
} else {
ApiSuccessResponse(body)
}
} else {
val msg = response.errorBody()?.string()
val errorMessage = if(msg.isNullOrEmpty()) {
response.message()
} else {
msg
}
ApiErrorResponse(errorMessage ?: "Unknown error")
}
}
}
}
class ApiSuccessResponse<T>(val data: T): ApiResponse<T>()
class ApiSuccessEmptyResponse<T>: ApiResponse<T>()
class ApiErrorResponse<T>(val errorMessage: String): ApiResponse<T>()
Where you just need to call create with the response as ApiResponse.create(response) and it should return correct type. A more advanced scenario could be added here as well, by parsing the error if it is not just a plain string.

This question came up in the pull request where suspend was introduced to Retrofit.
matejdro: From what I see, this MR completely bypasses call adapters when using suspend functions. I'm currently using custom call adapters for centralising parsing of error body (and then throwing appropriate exceptions), smilarly to the official retrofit2 sample. Any chance we get alternative to this, some kind of adapter that is injected between here?
It turns out this is not supported (yet?).
Source: https://github.com/square/retrofit/pull/2886#issuecomment-438936312
For error handling I went for something like this to invoke api calls:
suspend fun <T : Any> safeApiCall(call: suspend () -> Response<T>): MyWrapper<T> {
return try {
val response = call.invoke()
when (response.code()) {
// return MyWrapper based on response code
// MyWrapper is sealed class with subclasses Success and Failure
}
} catch (error: Throwable) {
Failure(error)
}
}

Related

How to create dummy ResponseBody object which required by OkHttp?

I am passing null right now, which causes a crash!
See: val response: Response<ReviewResponse> = Response.error(-1, null)
Code:
suspend fun getReviewData() = getResult {
try {
apiService.getReviewData(getCustomerId())
} catch (e: Exception) {
val response: Response<ReviewResponse> = Response.error(-1, null)
response
}
}
As you can see null is not accepting internally, and I must need pass this: ResponseBody body
What about this:
Response.error(404, ResponseBody.create(null, "Not found response body"))
You can create a result data class like this
data class ApiResult<out T>(
val status: Status,
val data: T?,
val error: Throwable?,
val message: String?
) {
enum class Status {
SUCCESS,
ERROR,
LOADING
}
companion object {
fun <T> success(data: T?): ApiResult<T> {
return ApiResult(Status.SUCCESS, data, null, null)
}
fun <T> error(message: String, error: Throwable?): ApiResult<T> {
return ApiResult(Status.ERROR, null, error, message)
}
fun <T> loading(data: T? = null): ApiResult<T> {
return ApiResult(Status.LOADING, data, null, null)
}
}
override fun toString(): String {
return "Result(status=$status, data=$data, error=$error, message=$message)"
}
}
and then create your custom base response like this
data class CommonResponse<T>(
#SerializedName("error") val error: Boolean,
#SerializedName("status") val status: Int,
#SerializedName("message") val message: String?,
#SerializedName("response") val response: T?
)
and assign them like this in retrofit
suspend fun <T> getResponse(
request: suspend () -> Response<T>
): ApiResult<T> {
return try {
val result = request.invoke()
if (result.isSuccessful) {
return ApiResult.success(result.body())
} else {
ApiResult.error("Error", null)
}
} catch (e: Throwable) {
ApiResult.error("Unkown Error", e)
}
}
and use them like this in call
interface CheckWhereApi {
//Check Where API
#GET("url")
suspend fun checkWhere(): Response<CommonResponse<MyModel>>
}
Depend on your requirement
addBody() function you are not passing any param here. need to pass param.
look like api construction in your code missing .
please follow link to know more-
https://www.chillcoding.com/android-retrofit-send-http/
++ update
depend on your comment i think you not get direct answer , i am not give direct answer, its depend on exact architecture you following.
little bit more info
java.lang.IllegalArgumentException: code < 400: -1
its define architecture.
if (code < 400) throw new IllegalArgumentException("code < 400: " + code);
here i suggest you how you going to return result its quite complicated , you try with some custom class with error handle and success handle.
data class ResponseByApi(val success: Any, val code: Int, val
error : Any)
create response model class and set value as per network response
like success set success body and code else if fail set error body and code -> return as per response.
I see that the code looks:
public static <T> Response<T> error(int code, ResponseBody body) {
Objects.requireNonNull(body, "body == null");
if (code < 400) throw new IllegalArgumentException("code < 400: " + code);
return ... // build error response
}
And you call it:
val response: Response = Response.error(-1, null)
Thus, it will fail by NullPointerException.
Even if you comment on this line, it will fail by IllegalArgumentException because the code is less than 400.
However, you need to return Response<ReviewResponse> type.
You could use ResponseEntity for this:
new ResponseEntity<>(HttpStatus.OK);
It will be exactly creating dummy ResponseBody object which required by OkHttp. But you need to use ResponseEntity instead of Response.
Or you could throw exception, like:
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "entity not found"
);
From org.springframework.web.server.ResponseStatusException

Retrofit Post on Android kotlin not working but working on postman

I am trying to Hit the API using the Retrofit Post method to put the form data but the problem is that the API returns null all the time even if I try to pass the params. I tested the API by running it in Postman it works fine and I also added internet permission and set usesCleartextTraffic to true under manifest.xml, so it would be really helpful if anyone guides me to rectify this issue.
Output When I tried to debugg
call = Unresolved reference: call
response = Unresolved reference: response
this = {MainActivity#9661}
params = {HashMap#10022} size = 5
"password" -> "Ram#123"
"countryCode" -> {Integer#10042} 91
"name" -> "Ranjith"
"mobile" -> {Long#10046} 99********
"emailID" -> "var***#gmail.com"
Response back from the API with body null all the time
response = {Response#10220} "Response{protocol=http/1.1, code=400, message=Bad Request, url=http://*.***.***.***:***0/api/v1/****/auth/register}"
body = null
errorBody = {ResponseBody$1#10224}
content = {Buffer#10228} "[text={"code":400,"error":true,"msg":"Enter a valid name"}]"
contentLength = 52
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
uploadBt.setOnClickListener {
callApi()
}
}
private fun callApi() {
val params = HashMap<String?, Any?>()
params["name"] = "Ranjith"
params["mobile"] = 99*******
params["emailID"] = "var***#gmail.com"
params["password"] = "Ram#123"
params["countryCode"] = 91
RetrofitClient.instance.registerUser(params)
.enqueue(object : Callback<DefaultResponse>{
override fun onResponse(call: Call<DefaultResponse>, response: Response<DefaultResponse>) {
print(response)
if(response.isSuccessful)
{
Toast.makeText(applicationContext, response.body()?.msg, Toast.LENGTH_SHORT).show()
}else
{
var jsonObject: JSONObject? = null
try {
jsonObject = JSONObject(response.errorBody()!!.string())
val code = jsonObject.getString("code")
val error = jsonObject.getString("error")
val message = jsonObject.getString("msg")
Toast.makeText(applicationContext, message+"\n"+code+"\n"+error, Toast.LENGTH_LONG).show()
} catch (e: JSONException) {
Toast.makeText(applicationContext, e.toString(), Toast.LENGTH_LONG).show()
e.printStackTrace()
}
}
}
override fun onFailure(call: Call<DefaultResponse>, t: Throwable) {
Toast.makeText(applicationContext, t.toString(), Toast.LENGTH_SHORT).show()
}
})
}
}
Interface Api
interface Api {
#FormUrlEncoded
#POST("/api/v1/******/****/register")
fun registerUser(
#FieldMap params: HashMap<String?, Any?>
): Call<DefaultResponse>
}
RetrofitClient.kt object class
object RetrofitClient {
private const val BASE_URL = "http://3.1**.**5.1*1:****"
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
.method(original.method(), original.body())
val request = requestBuilder.build()
chain.proceed(request)
}.build()
val instance: Api by lazy{
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
retrofit.create(Api::class.java)
}
}
DefaultResponse.kt model data class
data class DefaultResponse(
#SerializedName("code")
val code: Int,
#SerializedName("err")
val error: Boolean,
#SerializedName("msg")
val msg: String
)
Through Postman

Creating Live Data for Auth Token in Account Manager

I am trying to create live data for the authToken in AccountManager.
This is how I am getting the auth token.
suspend fun Fragment.getAuthToken(): String? {
val am: AccountManager = AccountManager.get(activity)
val accounts: Array<out Account> = am.getAccountsByType(getAccountType())
var authToken: String? = null
if (accounts.isNotEmpty()) {
val account = accounts.first()
withContext(Dispatchers.IO) {
authToken = am.blockingGetAuthToken(account, getAccountType(), true)
}
}
return authToken
}
According to the documentation I should do something like this:
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() {
stockManager.removeUpdates(listener)
}
}
However I can't figure out how to convert the example to match my case.
According to another link
you can use live-data builder for coroutine:
val token: LiveData<String> = liveData {
val tokenValue = someYourFragment.getAuthToken()
emit(tokenValue)
}

How to use Retrofit2

I called my service with AsyncTask and DefaultHttpClient before but now i want to use Retrofit. I read about this and implement that and onResponse method called but my response has Bad Request Message and can't get body of response.
My error is:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.retrofitsample.f.retrofitsample/com.retrofitsample.f.retrofitsample.MainActivity}: java.lang.IllegalArgumentException: Unable to create converter for com.retrofitsample.f.retrofitsample.model.HttpResponse<com.retrofitsample.f.retrofitsample.model.MS>
Caused by: java.lang.IllegalArgumentException: class com.retrofitsample.f.retrofitsample.model.MS declares multiple JSON fields named Id
I have a wcf service like below :
[OperationContract]
[WebInvoke(Method = "POST",
ResponseFormat = WebMessageFormat.Json,
RequestFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.WrappedRequest,
UriTemplate = "GetMS")]
HttpResponse<MS> GetMS(string token,string Id);
I have a HttpResponse and all of my services return a type of T.. I write belo code in client side:
MyActivity:
retrofit2.Call<HttpResponse<MS>> call=service.getAllMonitoringScheduale("971048F6-7ABA-4060-8CC3-BC57EC259FA3","1292");
call.enqueue(new Callback<HttpResponse<MS>>() {
#Override
public void onResponse(retrofit2.Call<HttpResponse<MS>> call, Response<HttpResponse<MS>> response) {
Log.e("Response=",response.body().getResultMessage());
}
#Override
public void onFailure(retrofit2.Call<HttpResponse<MS>> call, Throwable t) {
Toast.makeText(MainActivity.this, "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show();
}
});
My Interface:
public interface GetDataService {
#POST("GetMonitoringSchedule")
Call<HttpResponse<MS>> getAllMS(#Query("token")String token,#Query("Id") String Id);
}
and this is my models Class:
public class HttpResponse<T> {
#SerializedName("ResultMessage")
private String ResultMessage;
#SerializedName("Result")
private T Result;
public HttpResponse(String ResultMessage,T Result){
this.ResultMessage=ResultMessage;
this.Result=Result;
}
public String getResultMessage(){
return ResultMessage;
}
public void setResultMessage(String ResultMessage){
this.ResultMessage=ResultMessage;
}
public T getResult(){
return Result;
}
public void setResult(T Result){
this.Result=Result;
}
}
public class MS {
#SerializedName("Id")
public long Id;
#SerializedName("PId")
public long PId;
#SerializedName("SType")
public int SType;
#SerializedName("SDateF")
public Date SDateF ;
#SerializedName("SDateT")
public Date SDateT;
..Constructor and setter and getter
}
How to fix this errors and what is my problem?
I don't know how to use HttpResponse type in Retrofit2?
I have found a project that uses Retrofit in order to make network requests.
You can use it as a reference in order to understand how to use Retrofit.
https://github.com/AcademyTLV/fundamentals-2018-exercise/tree/ex-7-networking
Also be sure to use a POJO In order to create your classes to handle the request:
http://www.jsonschema2pojo.org/
Here i have fully example for it.
This dependancy add in gradle
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
annotationProcessor 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
Here now create ApiClient.kt file
object ApiClient {
val BASE_URL = "http://yourwebsite/services/"
private var retrofit: Retrofit? = null
val client: Retrofit
get() {
if (retrofit == null) {
retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit!!
}
}
Now create APIInterface.kt
#FormUrlEncoded
#POST("users/login")
fun POST_LOGIN(
#Field("imei_number") imei_number: String,
#Field("device_token") device_token: String,
#Field("mobile") mobile: String,
#Field("password") password: String
): Call<LoginResponse>
#GET("general/init-data")
fun GENERAL_MODULE(
#Header("Authorization") auth_key: String
): Call<InitResponse>
#GET("event-gallery/list")
fun GET_Event_GALLERY(
#Header("Authorization") auth_key: String
): Call<EventListResponse>
#GET("event-gallery/photo-list")
fun GET_Event_GALLERY_PHOTO(
#Header("Authorization") auth_key: String,
#Query("id") id: Int
): Call<EventGallerListResponse>
if Any Header for token the use #Header and also When call #GET that time params use #Query and #Post that time #Fields
Now Response file
data class EventListResponse(
#SerializedName("success")
var success: Boolean,
#SerializedName("data")
var data: EventgalleryModel?,
#SerializedName("server_error"),
#SerializedName("eventgallery")
var eventgallery: ArrayList<EventListData>
var server_error: Boolean,
#SerializedName("message")
var message: String
)
Then create Model class of Response
Now time to Activity code
private fun loadData() {
card_progress.visibility = View.VISIBLE
val apiService = ApiClient.client.create(ApiInterface::class.java)
val call =
apiService.GET_FEE_INSTALMENT_LIST(PreferenceManager.getAuthKey(this#FeesInstalmentActivity)!!)
call.enqueue(object : Callback<FeeInstalmentListResponse> {
override fun onResponse(
call: Call<FeeInstalmentListResponse>,
response: Response<FeeInstalmentListResponse>
) {
card_progress.visibility = View.GONE
val data = response.body()!!.data
if (response.code() == 200 && data != null) {
if (response.body()!!.server_error) {
txt_no_data_fee.visibility = View.VISIBLE
txt_no_data_fee.text = response.body()!!.message
} else {
Log.e("data", data.toString())
if (data != null && data.feesinstalment.isEmpty()) {
txt_no_data_fee.visibility = View.VISIBLE
} else {
txt_no_data_fee.visibility = View.GONE
adapter!!.setItem(data.feesinstalment)
}
}
} else if (response.code() == 401) {
PreferenceManager.removePref(this#FeesInstalmentActivity)
startActivity(
Intent(this#FeesInstalmentActivity, LoginActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
)
finish()
} else {
Toast.makeText(
this#FeesInstalmentActivity,
R.string.somethingWrong,
Toast.LENGTH_SHORT
).show()
}
}
override fun onFailure(call: Call<FeeInstalmentListResponse>, t: Throwable) {
card_progress.visibility = View.GONE
Log.e("onFailure", t.message)
txt_no_data_fee.visibility = View.VISIBLE
}
})
}
Sry i forget Adapter here
class FeeInstalmentAdapter(
private val context: Context,
private var items: ArrayList<FeeInstalmentListData>
) : RecyclerView.Adapter() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.row_fees_instalment_item, parent, false))
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.due_date.text = DateHelper.parseData(items[position].due_date!!, "yyyy-MM-dd", "dd MMM yyyy")
holder.instalment_title.text = items[position].instalment_title
if (items[position].paid_date == null) {
holder.paid_text.visibility = View.GONE
holder.paid_date.text = context.resources.getString(R.string.UnPaid)
holder.paid_date.setTextColor(Color.parseColor("#DC143C"))
} else {
holder.paid_date.text = DateHelper.parseData(items[position].due_date!!, "yyyy-MM-dd", "dd MMM yyyy")
holder.paid_date.setTextColor(Color.parseColor("#58A259"))
}
//holder.paid_date.text = items[position].paid_date
holder.amount.text = "Rs. " + items[position].amount
holder.amount.setTextColor(Color.parseColor("#ED7136"))
}
override fun getItemCount(): Int {
return items.size
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
fun setItem(holidays: ArrayList<FeeInstalmentListData>) {
items = holidays
notifyDataSetChanged()
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val due_date = view.due_date
val instalment_title = view.instalment_title
val paid_date = view.paid_date
val amount = view.amount
val paid_text = view.paidText
}
}

Callback get response Sync kotlin

I'm tring to get the response of a callback in sync mode because I the value of the response is needed to all application to work, without this value ( token ) I can't continue to use the application.
This is my companion object inside the retrofit interface. I need to set the token before creation of a client.
What I'm doing wrong?
EDIT :
I put as this Logs as you write :
companion object {
private var mToken: String = ""
fun create(activity: Activity): MyPhoneInterface {
Log.d("tokenMyPhoneInterface", activity.localClassName)
getToken(activity)
Log.d("tokenMyPhoneInterface", "client token $mToken")
val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Authorization", mToken).build()
chain.proceed(request)
}.build()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.baseUrl(BuildConfig.API_HOST)
.build()
return retrofit.create(MyPhoneInterface::class.java)
}
private fun getToken(activity: Activity) {
if(!activity.isFinishing && isJwtExpired(mToken)){
val latch = CountDownLatch(1)
(LoginManager(activity)).getToken(true, object : ServiceCallback<String> {
override fun success(token: String) {
Log.d("tokenMyPhoneInterface", "token $token")
mToken = token
latch.countDown()
}
override fun failure(serviceError: ServiceError) {
Log.d("tokenMyPhoneInterface", serviceError.errorMessage)
latch.countDown()
}
})
Log.d("tokenMyPhoneInterface", "before await ")
latch.await()
Log.d("tokenMyPhoneInterface", "after await")
}
}
}
But I the system is blocked in the latch.await() and the logs is :
05-14 18:19:00.127 848-848/com.italy.myphone D/tokenMyPhoneInterface: view.splash.activity.Splash
05-14 18:19:00.131 848-848/com.italy.myphone D/tokenMyPhoneInterface: before await
EDIT answer2:
sealed class TokenResult {
class Success(val token: String) : TokenResult()
class Error(val serviceError: ServiceError) : TokenResult()}
suspend fun getToken(activity: Activity): TokenResult {
return suspendCancellableCoroutine { continuation ->
(LoginManager(activity)).getToken(true, object : ServiceCallback<String> {
override fun success(token: String) {
continuation.resume(TokenResult.Success(token))
}
override fun failure(serviceError: ServiceError) {
continuation.resume(TokenResult.Error(serviceError))
}
})
}}
And this is how to I try to call the suspend function :
companion object {
fun create(activity: Activity): MyPhoneInterface{
Log.d("tokenMyPhoneInterface", activity.localClassName)
var token = runBlocking {
return#runBlocking getToken(activity)
}
Log.d("tokenMyPhoneInterface", "obtained token")
Log.d("tokenMyPhoneInterface", "client token $tokenResult")
val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Authorization", "").build()
chain.proceed(request)
}.build()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.baseUrl(BuildConfig.API_HOST)
.build()
return retrofit.create(MyPhoneInterface::class.java)
}
}
That is inside an interface and this is the code that I use to call the interface/companion object in the activity :
private val mMyPhoneInterface by lazy {
MyPhoneInterface.create(activity)
}
The best way I know to get the response of a callback in sync mode is using
Coroutines and the function suspendCancellableCoroutine
In your case you can have this function:
suspend fun getToken(activity: Activity): TokenResult {
return suspendCancellableCoroutine { continuation ->
(LoginManager(activity)).getToken(true, object : ServiceCallback<String> {
override fun success(token: String) {
continuation.resume(TokenResult.Success(token))
}
override fun failure(serviceError: ServiceError) {
continuation.resume(TokenResult.Error(serviceError))
}
})
}
}
sealed class TokenResult {
class Success(val token: String) : TokenResult()
class Error(val serviceError: ServiceError) : TokenResult()
}
And in your activity.onCreate:
override fun onCreate(savedInstanceState: Bundle?) = runBlocking {
super.onCreate(savedInstanceState)
val tokenResult = getToken(this)
if(tokenResult is Error){
finish()
return#runBlocking
}
//create client here with tokenResult.token value
}
Give it a try and let me know...
EDIT: In the example I use runBlocking because getToken is a suspend function. In your own code you should handle this logic outside the activity.
EDIT:
To eneable coroutines in your project add the following lines in your gradle file:
dependencies {
//other dependencies
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.22.5"
}
kotlin {
experimental {
coroutines "enable"
}
}
At the end I use JavaRx in order to sync a callback.. this is the snippet.
fun getToken(loginManager: LoginManager): String {
return Single
.create(SingleOnSubscribe<String> { emitter ->
loginManager.getToken(object : TokenSimpleCallback {
override fun onSuccess(token: String) {
emitter.onSuccess(token)
}
override fun onFailure(loginServiceError: LoginServiceError) {
emitter.onError(Throwable(loginServiceError.toString()))
}
})
}).blockingGet()}
You shouldn't use latch.count. It is equals to 1 and latch.count > 1 is false. Then your function is returned. Just use latch.await() and it will await for one of callbacks.
Sorry if I am wrong, I don't have enough repo to comment.

Categories

Resources