how to fetch correctly XML from a server in Kotlin - android

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.

Related

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()
}

Retrofit upload is storing form data inside the upload file, corrupting it

I am using Retrofit 2 to upload an audio file to an Azure blob storage service via Azure's REST APIs.
The upload appears to work, but the file stored in the Azure blob container is corrupt because as well as the audio data it contains what appears to be HTTP headers. For example, these are the contents of one uploaded file:
--3c88cdb1-5946-432d-a129-cc8e930d014c
Content-Disposition: form-data; name="tape";
filename="/data/user/0/blahblah.mp4"
Content-Type: audio/mp4
Content-Length: 8365
...expected binary data blah blah blah ....
--3c88cdb1-5946-432d-a129-cc8e930d014c--
What am I doing wrong?
My upload function looks like this:
val tapeFile = File(fileName)
val tapePart = tapeFile.asRequestBody("audio/mp4".toMediaType())
val tapeBodyPart = MultipartBody.Part.createFormData("tape",tapeFile.absolutePath, tapePart)
tapeAzureWebService.uploadTape(url, tapeBodyPart).enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
etc etc
My Retrofit interface interface is like this:
#Multipart
#PUT
fun uploadTape(#Url url: String,
#Part tape: MultipartBody.Part): Call<ResponseBody>
(It's using #URL because I'm using Azure SAS, with dynamic URLs with authentication embedded in the URL as a series of query strings, and that works very well and is a neat hint for anyone who stumbles on this, by the way, since it prevents Retrofit from encoding the URL and query.)
And my OKHttp Client looks like this, adding some headers that Azure demands:
class TapeAzureWebServiceAPI {
fun service() : TapeAzureWebService {
val headerInterceptor = object: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val requestBuilder = original.newBuilder()
.header("x-ms-version", "2015-12-11")
.header("x-ms-blob-type","BlockBlob")
val request = requestBuilder.build()
return chain.proceed(request)
}
}
val loggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
logI("retrofit: $message")
}
}).setLevel(HttpLoggingInterceptor.Level.BODY)
val client : OkHttpClient = OkHttpClient.Builder().apply {
this.addInterceptor(headerInterceptor)
this.addInterceptor(loggingInterceptor)
}.build()
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(AZURE_URL)
.client(client)
.build()
return retrofit.create(TapeAzureWebService::class.java)
}
}
If I use a simple RequestBody rather than a multipart form, I still get the same corruption to the audio file, though there are fewer headers in the audio file.
I've looked at this a long time, and I can't tell if it's something I'm doing wrong in Retrofit, whether Azure wants different headers, or whether Azure simply doesn't like multipart form data.
thanks
John
remove #Multipart just add,
#Headers( "x-ms-blob-type: BlockBlob", "x-ms-blob-content-type: image/png")
#PUT
suspend fun uploadDocument(#Url url: String, #Body request: RequestBody)
and pass request body as,
val mediaType = "image/png".toMediaTypeOrNull()
val body = yourImageFile.asRequestBody(mediaType)

Retrofit2 authentication error to IBM's Speech to Text

I am trying to access IBM's Speech to Text service without using the library. I am using Retrofit with GSON.
The issue is in the authentication, which apparently does not occur correctly, returning code 401. From the official documentation, the HTTP request should come in this format
curl -X POST -u "apikey:{apikey}" \
--header "Content-Type: audio/flac" \
--data-binary #{path_to_file}audio-file.flac \
"{url}/v1/recognize"
When I test the curl command with my credentials, the service works fine.
This is the interface I'm using
interface SpeechToTextApi {
#Multipart
#POST("v1/recognize")
fun speechToText(
#Header("Authorization") authKey: String,
#Part("file") filename: RequestBody,
#Part voiceFile: MultipartBody.Part
): Call<List<SpeechToText>>
}
where I have the following data classes
data class SpeechToText(val results: List<SttResult>)
data class SttResult(val alternatives: List<RecognitionResult>, val final: Boolean)
data class RecognitionResult(val confidence: Float, val transcript: String)
and this is how I set up Retrofit
private val retrofit = Retrofit.Builder()
.baseUrl(STT_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
private val service = retrofit.create(SpeechToTextApi::class.java)
while calling the actual service looks like this
val requestFile = RequestBody.create(MediaType.parse("audio/mp3"), file.name)
val body = MultipartBody.Part.createFormData("file", file.name, requestFile)
service
.speechToText(getString(R.string.stt_iam_api_key), requestFile, body)
.enqueue(object: Callback<List<SpeechToText>> {
override fun onResponse(call: Call<List<SpeechToText>>, response: Response<List<SpeechToText>>) {
val listOfStts = response.body()
Log.d(TAG, "Response code: ${response.code()}")
if (listOfStts != null) {
for (stt in listOfStts) {
for (res in stt.results) {
Log.d(TAG, "Final value: ${res.final}")
for (alt in res.alternatives) {
Log.d(TAG, "Alternative confidence: ${alt.confidence}\nTranscript: ${alt.transcript}")
Toast.makeText(this#MainActivity, alt.transcript, Toast.LENGTH_SHORT).show()
}
}
}
}
}
override fun onFailure(call: Call<List<SpeechToText>>, t: Throwable) {
Log.d(TAG, "Error: ${t.message}")
t.printStackTrace()
}
})
Recordings are MP3 files, for which I am sure they are stored correctly and accessible. I have replaced audio/flac with audio/mp3 as well.
Issue seems to be in the way authentication works. Prior to the code I have shown above, I've used
private val retrofit = Retrofit.Builder()
.baseUrl(STT_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request()
val headers = request
.headers()
.newBuilder()
.add("Authorization", getString(R.string.stt_iam_api_key))
.build()
val finalRequest = request.newBuilder().headers(headers).build()
chain.proceed(finalRequest)
}
.build())
.build()
but the same response code 401 persisted. Of course, the interface method lacked the #Header parameter.
Any sort of help is much appreciated.
I am kind of saddened by the fact nobody was able to solve this one sooner, but here's the solution I came across by accident when working on a different project altogether.
As you can see from the curl command, authentication comes in the form of username: password pattern, in this case, username being apikey string and password is your API key.
So the way you should tackle this is by building your Retrofit instance this way:
fun init(token: String) {
//Set logging interceptor to BODY and redact Authorization header
interceptor.level = HttpLoggingInterceptor.Level.BODY
interceptor.redactHeader("Authorization")
//Build OkHttp client with logging and token interceptors
val okhttp = OkHttpClient().newBuilder()
.addInterceptor(interceptor)
.addInterceptor(TokenInterceptor(token))
.build()
//Set field naming policy for Gson
val gsonBuilder = GsonBuilder()
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
//Build Retrofit instance
retrofit = Retrofit.Builder()
.baseUrl(IBM_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
.client(okhttp)
.build()
}
and create this custom interceptor
class TokenInterceptor constructor(private val token: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val requestBuilder = original
.newBuilder()
.addHeader("Authorization", Credentials.basic("apikey", token))
.url(original.url)
return chain.proceed(requestBuilder.build())
}
}
You need to use Credentials.basic() in order to encode credentials.
I really hope somebody with a similar issue stumbles across this and saves themselves some time.

Retry using Retrofit2 + RxJava + Jackson for 202 Status Code

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()

Categories

Resources