Retry using Retrofit2 + RxJava + Jackson for 202 Status Code - android

I have an API which returns 200, 202, 4xx based on different scenarios. When I get a 202, I am supposed to make the same API until I get a 200 or 4xx. I tried using doOnErrorNext, onError, onNext. I was not able to crack the problem
Observable<MyPOJO> makeAPI();
Observable<MyPOJO> makeAPIImpl(){
makeAPI().doOnErrorNext(/*how to I access the error code here*/);
makeAPI().doOnNext(/*given that 202 is a success code, I expected it to come here, but it goes to Error because of JSON Mapping*/);
}
doOnErrorNext -> I was able to make the API call again but then it would happen for all the error scenarios which I dont want
I have checked multiple answers regarding this, but nobody has solved this combination specifically and am unable to incorporate other answers to my use case.

I would suggest you use OkHttp and use an interceptor to retry your request, something along these lines (this is from one of my apps in Kotlin, but it should give you the idea):
inner class ExpiredSessionInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
if (response.code() == 202) {
val newRequest = request.newBuilder().build()
return chain.proceed(newRequest)
} else {
return response;
}
}
}
then
val httpClientBuilder = OkHttpClient.Builder()
httpClientBuilder.addInterceptor(ExpiredSessionInterceptor())
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(SERVER_ENDPOINT_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.client(httpClientBuilder.build())
.build()

Related

Why do I get " I/system_server: oneway function results will be dropped but finished with status OK and parcel size 4 " message?

I am writing an android application using Kotlin ,Retrofit ,OKHttp and Rxjava when make a call to my API in order to add a new user to database i get the following message in logcat:
I/system_server: oneway function results will be dropped but finished with status OK and parcel size 4
The problem is that nothing happens no user is added in fact the API does not receive any requests from my app even thought i have the appropriate permissions and a android:usesCleartextTraffic="true" annotation in my AndroidManifest.xml.
By putting println() in various places in the code I was able to confirm that the function responsible for making the call is in fact executed but code responsible for handling response is never called and no error is reported.
Here is the code of above mentioned function:
private fun addUser(user: UserDataObject) {
val client = OkHttpClient.Builder()
.addInterceptor(BasicAuthInterceptor("admin", "admin")) // temporarily hardcoded
.build()
val requestInterface = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.client(client)
.build().create(GetData::class.java)
myCompositeDisposable?.add(
requestInterface.addUser(user)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::handleResponse, this::onError)
)
}
and the code of the interface:
interface GetData {
#POST("/api/some/API/URL")
fun addUser(#Body user: UserDataClass) : Completable}
also ( just in case ) the code of interceptor:
class BasicAuthInterceptor(username: String, password: String): Interceptor {
private var credentials: String = Credentials.basic(username, password)
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
var request = chain.request()
request = request.newBuilder().header("Authorization", credentials).build()
return chain.proceed(request)
}
}
Can you please tell me why is my app not able to communicate with API ?
While searching for answer on the internet i found this question so I gather that it has something to do with selinux permissions but this doesn't help me at all since I'm just a beginner.

what is retrofit interceptor , can we have multiple?

I can not understand the retrofit interceptors ,
private val OkHttpClient by lazy {
okhttp3.OkHttpClient.Builder()
.addInterceptor {
onOnIntercept(it)
}
.addInterceptor(LoggingInterceptor())
.addInterceptor(getInterceptor404())
.callTimeout(10, TimeUnit.MILLISECONDS)
// .addInterceptor(TimeoutInterceptor())
.build()
}
and what do these lines do, and If I have, multiple does the speed down?
val response: Response = chain.proceed(chain.request())
return chain.proceed(chain.request())
In Android sometimes you need to add a couple of parameters, like headers, to make a successful request, this is normal behavior from all the Android Apps when you are using Retrofit, you can do it in multiple ways
For example, you can add parameters directly to your request interface using the annotation Headers and putting a plain String, like this:
#Headers("Content-Type:application/json; charset=UTF-8")
#GET("yourwebsite/{someParam}/login")
fun logout(#Path("someParam") someParam: String?): Observable<LoginResponseViewModel>
Another solution is to send the Headers as a parameter to your interface function, using an annotation Header and sending a parameter, this gives you the possibility to have a custom parameter that you can manage from every request:
#Headers("Content-Type:application/json; charset=UTF-8")
#GET("yourwebsite/{someParam}/login")
fun logout(#Header(UUID.randomUUID().toString()) authToken: String?, #Path("someParam") someParam: String?): Observable<LoginResponseViewModel>**
Interceptor
A couple of people using Dagger probably will go for an Interceptor, you can have two types of interceptor:
The first one is using an interceptor directly in your Singleton, this will not give you versatility, but it will solve your problem faster, in this example, you can go for the chain object, get the request of the Retrofit call, get a new Builder and then add the Headers.
#Provides
#Singleton
fun getUnsafeOkHttpClient(): OkHttpClient {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val builder = OkHttpClient.Builder()
builder.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor { chain ->
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", UUID.randomUUID().toString())
.build()
chain.proceed(newRequest)
}
}
Yes, you can use multiple interceptors. When you do a request calling interface method using retrofit, your request go to the interceptor and then continue. In the interceptor you can rewrite or retry request. For example, you could add the access token in all request and refresh the token if is necessary, add the headers, another bodies, etc. When you received a response from api, the interceptor intercept the response too. But please, read the documentation to understand how it works. Have a nice coding!

Twitter oauth/request_token 200 code with empty response body

I'm implementing Twitter OAuth flows as per:
https://developer.twitter.com/en/docs/authentication/guides/log-in-with-twitter
I am getting a response back for the first step (oauth/request_token) which has a 200 code, but the response body is completely empty.
I'm using Retrofit to call the API, and have hooked up an interceptor OkHttpClient to debug the response like so:
val client = OkHttpClient.Builder().also { builder ->
builder.addInterceptor { chain ->
val request = chain.request()
val response = chain.proceed(request)
response
}
}.build()
Then setting up Retrofit like so:
Retrofit.Builder()
.baseUrl(TWITTER_AUTH_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
.create(TwitterAuthRetrofit::class.java)
.getRequestToken(
authorizationHeaders
).enqueue(object : Callback<TwitterRequestToken> {
override fun onResponse(call: Call<TwitterRequestToken>, response: Response<TwitterRequestToken>) {
onSuccess(response.body())
}
override fun onFailure(call: Call<TwitterRequestToken>, t: Throwable) {
onFailure()
}
})
When I debug in the interceptor, I can see the response is successful (200) but the response body is empty, which I think is causing my Gson deserialization to fail.
The result of calling response.body.contentLength() in the interceptor is -1.
The result of calling response.code in the interceptor is 200.
Here is the model I am attempting to deserialize the response body to:
data class TwitterRequestToken(
#SerializedName(value = "oauth_token")
val token: String,
#SerializedName(value = "oauth_token_secret")
val tokenSecret: String,
#SerializedName(value = "oauth_callback_confirmed")
val callbackConfirmed: Boolean
)
Note I am using #SerializedName to provide the keys for the response body, whilst the names of my properties are arbitrary to our app (we use camel case). I add a GsonConverterFactory to the Retrofit instance using the builder and have done this in the same way for many other requests before with no issues.
Here is the response I am getting from the API, which I am looking at via debugging in the interceptor above:
Response{protocol=h2, code=200, message=, url=https://api.twitter.com/oauth/request_token}
And here is the cause message from the Throwable I am getting in the onFailure callback from Retrofit:
com.google.gson.stream.MalformedJsonException:
Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
Has anyone got any idea what might cause this?
Finally figured it out, hope this helps someone in future...
The response body from the Twitter API for oauth/request_token isn't encoded as JSON; you will need to read it from the response buffer. Specifically, when implementing the API with Retrofit, you will want your Retrofit interface to return ResponseBody (rather than your custom class), remove GSON from the Retrofit builder and, in the onResponseCallback from Retrofit, write the following code to read the buffer to a string, then split the string on & to get each key val pair, then you can split each of these on = and make sure you have all 3 values before constructing your model:
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
response.body()?.also { body ->
body.source().readString(Charsets.UTF_8).split('&').map { param ->
param.split('=').let { keyVal ->
keyVal[0] to keyVal[1]
}
}.toMap().let { paramMap ->
val oauthToken = paramMap["oauth_token"]
val oauthTokenSecret = paramMap["oauth_token_secret"]
val oauthCallbackConfirmed = paramMap["oauth_callback_confirmed"]?.toBoolean()
if (oauthToken == null || oauthTokenSecret == null || oauthCallbackConfirmed == null) {
onFailure()
} else {
onSuccess(
TwitterRequestToken(
oauthToken,
oauthTokenSecret,
oauthCallbackConfirmed
)
)
}
}
} ?: onFailure()
}

how to fetch correctly XML from a server in Kotlin

In iOS development, when I fetch an URL that displays XML, I can parse the whole XML file and use its data in my code, but in Kotlin I tried fetching the same URL and it returns only the first XML tag, like if the rest was hidden in the main tag.
val urlString = URL_TO_FETCH_IN_HTTPS (String)
val client = OkHttpClient()
val request = Request.Builder().url(urlString).build()
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val body = response.body?.string()
println("RESPONSE " + body)
}
}
override fun onFailure(call: Call, e: IOException) {
println("Failure")
}
})
The response of this call is just
RESPONSE < ?xml version="1.0" encoding="UTF-8"?>< rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
Although the url is good, and the returned XML in a browser is also good.
So what do I do wrong in the code? How can I fetch the whole XML at this url?
I used the library OkHttp for fetching data from a URL
First of all to debug Okhttp i would suggest to add an interceptor :
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.HEADERS
logging.level = HttpLoggingInterceptor.Level.BODY
Then build you client like this to first add interceptor and have handle timeout :
client = OkHttpClient.Builder()
.addInterceptor(logging)
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
Then you can : client.newCall(request).enqueue(object : Callback { ...
By doing so you can easily debug Okhttp you will have on the log the request that you made with the parameter send + then response of the server with the code and the body. All call are writen in blue for better visibilitie.
if you still got probleme don't hesitate to ask.
More over you should look at retrofit2 it's an easy lib to handle all apicall.

Error in posting data to Rest API server with auth Token in android

I am trying to post data to REST API server with retrofit + RxJava . When I am trying to send data to server , it said " HTTP 500 Internal Server Error Occurred". But when the data is send with POSTMAN, it succeeded.
This is the function for sending data in Model.
// Encountering with 500 server error
fun postSchedule(data : ScheduleResponse , errorLD: MutableLiveData<String>){
Log.d("POST DATA", "${data.title} ${data.remindMeAt}" )
userClient.postScheduleItem(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.subscribe(object : io.reactivex.Observer<ServerResponse>{
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: ServerResponse) {
errorLD.value = t.status
}
override fun onError(e: Throwable) {
errorLD.value = e.message
}
})
}
This is my API interface
#Headers("Accept: application/json")
#POST("schedules")
fun postScheduleItem(#Body data: ScheduleResponse): Observable<ServerResponse>
This is the retrofit client.
val httpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val httpClient = OkHttpClient.Builder()
var dbInstance: TodoDB = TodoDB.getInstance(context)
var rxJavaAdapter = RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())
val retrofitBuilder =
Retrofit.Builder()
.baseUrl(AppConstants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(rxJavaAdapter)
fun <T> createService(serviceClass: Class<T>, authToken: String?): T {
if (!TextUtils.isEmpty(authToken)) {
val interceptor = AuthenticationInterceptor(authToken!!)
if (!httpClient.interceptors().contains(interceptor)) {
httpClient.addInterceptor(interceptor)
retrofitBuilder.client(httpClient.build())
}
}
return retrofitBuilder.build().create(serviceClass)
}
Please help me with this.Thank you.
Client side code is not enough to determine what causes the server to respond with 500. The best you can do is start debugging the issue.
There are several directions you can go:
If you have access to the server or know someone who does, you could debug the server and determine what causes the Internal server error. Maybe the server logs can help as well and you don't have to actually step through the server code.
If you don't have access to the server, you could look at the body of the server response. Maybe there's a detailed error description there in html, json or some other format that will help you find out the root cause.
If the above steps don't help then it's very useful that you know the request works with POSTMAN. You can compare the exact POSTMAN request with the exact Retrofit request, header by header,
line by line. To do that, you should first add your httpLoggingInterceptor to your okhttp client builder with
val httpClient = OkHttpClient.Builder().addNetworkInterceptor(httpLoggingInterceptor)
and look for the request log in logcat.
If you spot the differences between the working and the not working requests, then you should work your way through all the differences, and adjust the retrofit request by adding or modifying headers using okhttp interceptors so that, at the end, the retrofit request looks exactly the same as the POSTMAN request. I suggest you remove the AuthenticationInterceptor at first and simulate it "manually" with a custom interceptor and a hard coded auth token.
Retry the request every time you eliminate a difference to isolate the cause of the internal server error.
Hope this helps!

Categories

Resources