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()
""
}
}
}
Related
(For privacy purposes, I will be using very general terms)
Although I can make a GET request that takes in a body and returns a boolean successfully on Postman, as shown in this image
Postman Screenshot, I can't get it to work for my Android app.
I have an API interface with the code:
#GET("api/is_correct")
suspend fun isCorrect(
#Body email: Email
): Response<Boolean>
and a method in my view model as shown below
fun checkIfCorrect(input: String) {
val email = Email(input)
viewModelScope.launch {
try {
val response = RetrofitInstance.api.isCorrect(email)
Log.e(TAG, response.toString())
} catch (e: Exception){
Log.e(TAG, "error")
}
}
}
and this data class
data class Email (
#SerializedName("email")
val email: String
)
The log only prints out "error" when I call viewmodel.checkIfCorrect(...)
I've basically used the same process for all my other PUT, GET, and POST API calls. This is the only one that's causing trouble for me. I'm guessing it's because the response body for this particular api call isn't wrapped in { } and does not have a format like "result": true , the way other API responses do.
How can I fix this issue?
I've tried Response, Response, String, and Boolean as the return type for suspend fun isCorrect. I've also tried using Query("email") email: String and Path("email") email: String as the parameter for fun isCorrect even though my api endpoint does not require additional parameters in the URL, only the body.
If the api already exists there is not much you can do but try this as Retrofit may not support GET with body
/**
* import okhttp3.OkHttpClient
* import okhttp3.Request
* import okhttp3.RequestBody
* import okhttp3.Response
*/
val client = OkHttpClient().newBuilder()
.build()
val mediaType: MediaType = MediaType.parseMediaType("application/json")
val body: RequestBody = RequestBody.create(mediaType, "{ \"message\":\"MESSAGE\"}")
val request: Request = Request.Builder()
.url("http://localhost:8080/sample/public")
.method("GET", body)
.addHeader("Content-Type", "application/json")
.build()
val response: Response = client.newCall(request).execute()
I have built an interceptor and I have one API call response that returns a series of emojiis over and over again, with a size of over 25MB, so the app crashes when this particular network call is made. I want to exclude the emojiis in the response entirely, or just reduce the size down to one emojii.
I have written this interceptor and viewed the documentation: https://square.github.io/okhttp/4.x/okhttp/okhttp3/-response-body/as-response-body/
How do I return this value as the response body in Kotlin? Any help for the retrofit Interceptor would be appreciated.
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import okio.IOException
abstract class Interceptor() : Interceptor {
/** This interceptor compresses the HTTP request body. */
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest: Request = chain.request()
val response: okhttp3.Response = chain.proceed(originalRequest)
if (response.body != null) {
val byteResponse = response.body?.contentLength()!!
// return response.body?.contentLength:Long= -1L):ResponseBody
if (byteResponse > 500000) {
val shorter =( byteResponse -1L)
shorter.asResponseBody(). // this line is the problem as it can't just be cast to a response body return
}
}
return response
}
}```
Can GSON help here? If you are looking to read the response in an easier and cleaner way.
Example: https://medium.com/#pypriyank/consuming-rest-api-in-android-using-retrofit-and-gson-20268aadf0eb
I am trying to parse using Moshi Library for JSON Array using Kotlin Coroutines .
Code use
fun retrofitIndia(baseUrl : String) : Retrofit = Retrofit.Builder()
.client(clientIndia)
.baseUrl(baseUrl)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
I get issue while Parsing the data class for JSON Array . I have used same for JSON Object and it works fine but during array , it crashes
Below is the crash line
java.lang.IllegalArgumentException: Unable to create converter for java.util.ArrayList<data.india.Delta2>
I call from Globallaunch coroutine where it gets failed
Code :
GlobalScope.launch(Dispatchers.Main) {
val statsRequest = i.getStats()
try {
val response = statsRequest.await()
if(response.){
val statsResponse = response.body() //This is single object Tmdb Movie response
Log.i("stats",""+statsResponse)
}else{
Log.d("MainActivity ",response.errorBody().toString())
}
}catch (e: Exception){
Log.e("Exception",e.localizedMessage)
}
}
You should make the type just List<T>, Moshi only supports the collection interfaces, not the concrete collection classes like ArrayList<T>, LinkedList<T>, etc.. The same goes for other kinds of collections: use Set<T> instead of HashSet<T>, and Map<K, V> instead of HashMap<K, V>, etc..
I don't think the coroutines have anything with the parsing error, try following Reading Json lists using Moshi
Quick snippet will look something like:
// Single item declaration
class SingleListItem(val title: String, val number: Int)
private var listMyData = Types.newParameterizedType(MutableList::class.java, SingleListItem::class.java)
private val adapter: JsonAdapter<List<SingleListItem>> = Moshi.Builder().add(KotlinJsonAdapterFactory()).build().adapter(listMyData)
Inside my app i call some remote APIs that give me a response wrapped into a useless "root" json element. I Paste you an example of json response
{
"response": {
"status": {
//here status fields, common to all responses
},
"configuration": {
//here configurations fields
}
}
}
I'm using an Android studio extension for generate kotlin data classes (JsonToKotlinClass) and i obtain four kotlin classes:
-MyResponseClass, Response, Status, Configuration
where MyResponseClass is like
data class MyResponseClass(
val response: Response
)
there's a way to avoid creation of "Response" class by parsing only it's relative json content and get a MyResponseClass look like like
data class MyResponseClass(
val status:Status,
val configuration: Configuration
)
?
From the title of your question I am assuming that you want to automatically parse the json response to the to the simpler MyResponseClass
You can achieve this by using a type adapter for gson. In your case the adapter class will look similar to the following.
class MyResponseClassAdapter : JsonDeserializer<MyResponseClass> {
override fun deserialize(jsonElement: JsonElement, p1: Type?, p2: JsonDeserializationContext?): MyResponseClass {
val content = jsonElement.asJsonObject["response"]
return Gson().fromJson(content , MyResponseClass::class.java)
}
}
Then add this adapter to a gson instance and use that gson instance with your retrofit instance.
val gson = GsonBuilder()
.registerTypeAdapter(MyResponseClass::class.java, MyResponseClassAdapter ())
.create()
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("{base_url}")
.client(okHttp)
.build()
Yes, when you get the object, instead of converting it straight away, convert it like this
val jsonObject = JSONObject("data")
val innerObject = jsonObject.getJSONObject("status")
and then convert inner object with gson.
This can of course be minified like this:
val object = Gson().fromJson(JSONObject("response").getJSONObject("status"), MyResponseClass::class.java)
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