POST Retrofit 2x raw JSONArray String in Kotlin - android

How to post raw jsonArray string in kotlin using retrofit
im having timeout response on onFailure method
here is sample of string array i want to post
[{"username":"username4"},{"username":"username2"}]
here is my endpoint definition
#Headers("Content-Type: application/json;charset=UTF-8")
#POST("insert/createuser")
fun postuser(#Body logs:String ):Call<ArrRes>
here are my classes
class ArrRes{
#SerializedName("username")
#Expose
var username: String = ""
#SerializedName("message")
#Expose
var message: String = ""
#SerializedName("status")
#Expose
var status: String = ""
}
here is my posting method
var obj = JSONObject();
var arr = JSONArray();
for (i in 0 until 5){
obj.put("username","username${i}");
arr.put(obj);
}
Log.i("app:sync","${arr.toString()}")
mService!!.postuser(arr.toString()).enqueue(
object : Callback<LogResponse> {
override fun onFailure(call: Call<LogResponse>, t: Throwable) {
Log.i("app:retro:service", "onFailure ${t.message}")
}
override fun onResponse(call: Call<LogResponse>, response: Response<LogResponse>) {
if (response.isSuccessful()) {
Log.i("app:retro:service", "onResponse true ${response.body()!!.toString()}")
} else {
Log.i("app:retro:service", "onResponse false ${response.raw().toString()}")
}
}
}
)
here is sample success post using postman
Thanks for helping :)

I solve this issue by adding this dependencies:
implementation 'com.squareup.retrofit2:converter-scalars:$version'
There are multiple existing Retrofit converters for various data formats. You can serialize and deserialize Java objects to JSON or XML or any other data format and vice versa. Within the available converters, you’ll also find a Retrofit Scalars Converter that does the job of parsing any Java primitive to be put within the request body. Conversion applies to both directions: requests and responses.
https://futurestud.io/tutorials/retrofit-2-how-to-send-plain-text-request-body

Related

Retrofit adding extra slashes when I upload data

I'm developing an Android app using Retrofit to connect to a Spring Boot server.
When I update data, there are extra slashes and double quotes on the server.
This is the output of POST method. "open"
This is the output of PUT method. "\"open\""
I read a similar article and I'm guessing I encode twice, but I don't know where I'm doing it. Please help me.
This is the service class of Android.
#PUT("/posts/close/update/{id}")
fun updateClose(#Path("id") id: Long, #Body close: String): Call<ResponseBody>
This is the view.
onClick = {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://*****.com")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
thread {
try {
val service: PostService =
retrofit.create(PostService::class.java)
service.updateClose(6, "open")
.enqueue(object : Callback<ResponseBody> {
override fun onResponse(
call: Call<ResponseBody>,
response: Response<ResponseBody>
) {
Log.d("Response is", "${response.body()}")
}
override fun onFailure(
call: Call<ResponseBody>,
t: Throwable
) {
Log.d("Hi", "error")
}
})
} catch (e: Exception) {
Log.d("response-weather", "debug $e")
}
}
This is the repository of Spring Boot.
#Modifying
#Transactional
#Query("UPDATE posts SET close = :close where post_id = :id", nativeQuery = true)
fun updateClose(#Param("id") id: Long, #Param("close") close: String)
Thank you very much.
There is nothing wrong with the data or the android side.
Strings in JSON must be written in double quotes. For more info refer this page.
Your JSON data is {"name": "Ken", "uid": "12345"}
In order to use double quotes inside a string you have to escape it via a backslash. For more info refer this question.
That's the reason for the extra backslashes.
I tried to load the json string via python and it worked like a charm. Attaching screenshot for reference. So any backend you would be using will be able to parse the JSON String.
Finally, I got the codes which work fine.
Service Class of Android.
#PUT("/posts/close/update/{id}")
fun updateClose(#Path("id") id: Long, #Query("close") close: String): Call<ResponseBody>
Controller class of Spring Boot. Before, I used #RequestBody instead of #RequestParam.
#PutMapping("/posts/close/update/{id}")
fun updateClose(#PathVariable id: Long, #RequestParam close: String) = postService.updateClose(id, close)

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

Retrofit2 strange combination of different braces in front of request Body

I am using Retrofit2 and I got stuck on the problem. I wrote simple entity for body:
data class DateRequest(
#JsonAdapter(RetrofitDateSerializer::class)
#SerializedName("date") #Expose val date: OffsetDateTime)
also I wrote custom serializer for it:
class RetrofitDateSerializer : JsonSerializer<OffsetDateTime> {
override fun serialize(
srcDate: OffsetDateTime?,
typeOfSrc: Type?,
context: JsonSerializationContext?
): JsonElement? {
val formatted = DateTimeUtil.convertFromDateTime(srcDate!!)
return JsonPrimitive(formatted)
}}
DateTimeUtil:
fun convertFromDateTime(dateTime: OffsetDateTime): String {
val formatter = formatDateTime()
return formatter.format(dateTime)
}
fun formatDateTime(): DateTimeFormatter {
return DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss").withLocale(Locale.US)
}
and in request body somehow appears this:
)]}'{"date": "2018-12-07 06:00:00"}
How this ")]}'" could be attached at front of my "date" json in request?
#POST("changecleaning/{userId}/{cleaningId}/{status}")
fun changeCleaning(
#Path("userId") userId: Long,
#Path("cleaningId") cleaningId: Long,
#Path("status") status: Int,
#Body date: DateRequest
): Maybe<Status>
Only that I found is after JsonWriter do some magic in buffer.readByteString() it stores broken body.
GsonRequestBodyConverter:
#Override public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
I found where was the problem comes from:
Here my custom GsonBuilder:
private fun getGsonFactory(): Gson {
return GsonBuilder()
.setLenient()
.serializeSpecialFloatingPointValues()
.setLongSerializationPolicy(LongSerializationPolicy.DEFAULT)
.generateNonExecutableJson()
.enableComplexMapKeySerialization()
.serializeNulls()
.setPrettyPrinting()
.registerTypeAdapter(CleaningProgress::class.java, CleaningProgressDeserializer())
.create()
}
The line of code below, add that ")]}'" at front of request. Why am I add it to builder? I trust official documentation about this method:
Makes the output JSON non-executable in Javascript by prefixing the generated JSON with some special text. This prevents attacks from third-party sites through script sourcing.
.generateNonExecutableJson()
Now my GsonBuilder looks like this:
private fun getGsonFactory(): Gson {
return GsonBuilder()
.serializeNulls()
.registerTypeAdapter(CleaningProgress::class.java, CleaningProgressDeserializer())
.create()
}

Retrofit - add field dynamically during serialization

I'm currently using Retrofit 2.3 in my Android project and recently the API we are using was updated so that it needs to have "version":number in JSON body in all POST requests. So let's say we need to pass UserCredentials object - previously body of the request was simply serialized using GSON converter and looked like this
{"username":"myUsername", "password":"myPassword"}
and now it has to have additional "version" field:
{"username":"myUsername", "password":"myPassword", "version":1}
I've googled couple of hours how to set custom converter factory to retrofit but all I found was how to exclude certain fields from serialization. I know I could simply add "version" field to all my POJOs but I found this approach 'dirty' as it's going to be used only during sending data to server.
Has anyone did something like this previously?
I did it but not exactly the way you want, you can create BaseRequest POJO class in which you can use version number and extend that class to other POJO classes you are using like this :
class BaseRequest{
#SerializedName("version")
public int version= 0;
}
Extend this base POJO class is to other POJO classes to use the version number like this :
class UserRequest extends BaseRequest{
#SerializedName("username")
public String userName = "";
#SerializedName("password")
public String password = "";
}
There are lots of benefits of this approach like if you need one more field in your APIs then you don't need to change all of the apis. You can achieve that just by adding a field in your baserequest.
Resolved this issue with custom interceptors for OkHTTP client. So I've created custom interceptor that would analyze outgoing request and alter its body JSON data if it meets certain criteria.
okHttpClientBuilder.addInterceptor(CustomInterceptor(1))
Works like a charm!
import okhttp3.Interceptor
import okhttp3.RequestBody
import okhttp3.Response
import okio.Buffer
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
class CustomInterceptor (private val version: Int): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val requestBody = request.body()
val subtype = requestBody?.contentType()?.subtype()
if(subtype != null
&& subtype.contains("json")) {
val bodyWithToken = addVersionParamToJSONBody(requestBody)
if(null != bodyWithToken){
val requestBuilder = request.newBuilder()
request = requestBuilder
.post(bodyWithToken)
.build()
}
}
return chain.proceed(request)
}
private fun addVersionParamToJSONBody(requestBody: RequestBody?) : RequestBody? {
val customRequest = bodyToString(requestBody)
return try{
val jsonObject = JSONObject(customRequest)
jsonObject.put("version", version)
RequestBody.create(requestBody?.contentType(), jsonObject.toString())
} catch (e: JSONException){
e.printStackTrace()
null
}
}
private fun bodyToString(requestBody: RequestBody?): String{
return try {
val buffer = Buffer()
requestBody?.writeTo(buffer)
buffer.readUtf8()
} catch (e: IOException){
e.printStackTrace()
""
}
}
}

Retrofit 2 - Response body null when response status is 422 (unprocessable entity)

I'm using Retrofit to make a POST Request in my web server.
However, I can't seem to get the response body when the response status is 422 (unprocessable entity). The response body is always null.
I want to know if I'm doing something wrong or if there's a workaround for this. Because I'm using the same json in the request with Postman, and it returns the body normally.
This is the method:
#Headers("Content-Type: application/vnd.api+json")
#POST("my_endpoint")
Call<JsonObject> postEntry(#Header("Authorization") String authorization, #Body JsonObject json);
The body is a JsonObject, I'm not serializing like the documentation say. But I don't think this is the problem.
By default, when your server is returning an error code response.body() is always null. What you are looking for is response.errorBody(). A common approach would be something like this:
#Override
public void onResponse(Response<JsonObject> response, Retrofit retrofit) {
if (response.isSuccess()) {
response.body(); // do something with this
} else {
response.errorBody(); // do something with that
}
}
If you need something advanced take a look at Interceptors and how to use them
I got the same error. My API was working using POSTMAN request but not working from Android retrofit call.
At first I was trying using #Field but it was getting error but later I've tried with #Body and it worked.
Sample Retrofit interface call
#POST("api/v1/app/instance")
Call<InstanceResponse> updateTokenValue(
#HeaderMap Map<String, String> headers,
#Body String body);
and API calling code is:
Map<String, String> headerMap=new HashMap<>();
headerMap.put("Accept", "application/json");
headerMap.put("Content-Type", "application/json");
headerMap.put("X-Authorization","access_token");
Map<String, String> fields = new HashMap<>();
fields.put("app_name", "video");
fields.put("app_version", "2.0.0");
fields.put("firebase_token", "token");
fields.put("primary", "1");
ApiInterface apiInterface = ApiClient.getApiClient().create(ApiInterface.class);
Call<InstanceResponse> call = apiInterface.updateTokenValue(
headerMap,new Gson().toJson(fields));
Well in this case you'll have to convert the response.
Have a look at this link
All the steps are already provided in the link above.
For Kotlin users here is the code solution.
ErrorResponse.kt (This obviously depends on your error response)
import com.squareup.moshi.Json
data class ErrorResponse(
#Json(name="name")
val name: String? = null,
#Json(name="message")
val message: String? = null,
#Json(name="errors")
val errors: Errors? = null,
#Json(name="statusCode")
val statusCode: Int? = null
)
ApiFactory.kt (Let me know if you need the entire code)
fun parseError(response: Response<*>): ErrorResponse {
val converter = ApiFactory.retrofit()
.responseBodyConverter<ErrorResponse>(
ErrorResponse::class.java, arrayOfNulls<Annotation>(0)
)
val error: ErrorResponse
try {
error = converter.convert(response.errorBody()!!)!!
} catch (e: IOException) {
e.printStackTrace()
return ErrorResponse()
}
return error
}
and in the Presenter (I use MVP)
GlobalScope.launch(Dispatchers.Main) {
try {
val response = ApiFactory.apiService.LOGIN(username, password)
.await()
val body = response.body()
body?.let {
// Handle success or any other stuff
if (it.statusCode == 200) {
mView.onSuccess(it.data!!)
}
} ?:
// This is the else part where your body is null
// Here is how you use it.
// Pass the response for error handling
mView.showMessage(ApiFactory.parseError(response).message!!)
} catch (e: Exception) {
e.printStackTrace()
}
}
And thats how you roll it!
That's All Folks!

Categories

Resources