Retrofit 2 POST XML and get JSON answer from API. with Kotlin - android

I need to send XML to server and I want server sends me back JSON.
I'am using Retrofit 2 + Kotlin
Retrofit2 method(in methods interface):
#Headers("Content-Type: application/xml; charset=urf-8",
"Accept: application/json")
#POST(Connectors.SECRET_LINK)
fun sendCustomXml(#Body data: XmlHolder): Observable<String>
Retrofit2 service:
private fun <S> createService(serviceClass: Class<S>): S {
val retrofit =
return
}
init {
initLoggingInterceptor()
val builder = Retrofit.Builder()
.baseUrl(BuildConfig.API_URL)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(SimpleXmlConverterFactory.create()) //here it is I thought
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
mService = builder.client(getHttpClient()).build().create(ApiMethods::class.java))
}
XML object I need to send like XML(tags and so on):
#Root(name = "root_element")
class XmlHolder {
#Attribute(name = "xmlns")
private val mXmlns = "http://www.anr.ru/types"
#Attribute(name = "type")
private val mType = "request"
#Element(name = "data")
private val mData = Data()
inner class Data {
#Attribute(name = "code")
private val mCode = "P0116"
#Element(name = "list_model")
private val mListModel = "123"
}
}
for now I got 400 error from server. shit.
Jake Wharton, can you suggest any solutions to me? :)
Any help, any examples, please, people...

Related

How to convert similar Json objects to Json Arrays with Gson and Retrofit

Let's say I'm getting below response from API
{"success":true,
"base":"EUR",
"date":"2021-04-16",
"rates":
{"AED":4.393943,"AFN":92.83145,"ALL":123.125765,"AMD":621.059723}}
and I want the last rates object to be a List of Rate class i.e. Rate(code="AED", value="4.393943"). I am using Retrofit and Gson. I know I need a Deserializer or a Type Adapter. I just don't know how to write one.
Rate Class:
data class Rate(
val code:String,
val value:Float
)
Currency Class:
data class Currency(
#SerializedName("success")
val success: Boolean?,
#SerializedName("base")
val base: String?,
#SerializedName("date")
val date: Date?,
#SerializedName("rates")
val rates: List<Rate>?
)
This is the request:
val gson: Gson = GsonBuilder()
.registerTypeHierarchyAdapter(Rate::class.java, TypeAdapterOrDeserializer())
.create()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.exchangerate.host")
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val jsonPlaceholderApi = retrofit.create(JsonPlaceholderApi::class.java)
val call = jsonPlaceholderApi.currency
When I request data from api it gives this error, I know why ;) as I have given List of Rates in Currency class but it is a json rate object.
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT
Thank you for the help :)
The data for rates returned by the API is a JSON object, but in your data class you defined it as a List so Gson expects it to be a JSON array. You would need custom parsing for your Currency class in this case.
Use this deserializer:
class CurrencyDeserializer : JsonDeserializer<Currency> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Currency {
if (json == null || context == null) {
// handle error here
throw Exception("Error")
}
val obj = json.asJsonObject
// let Gson handle the other 3 properties
val success = context.deserialize<Boolean?>(obj.get("success"), Boolean::class.java)
val base = context.deserialize<String?>(obj.get("base"), String::class.java)
val date = context.deserialize<Date?>(obj.get("date"), Date::class.java)
// create List<Rate> from the rates JsonObject
val ratesSet = obj.get("rates").asJsonObject.entrySet()
val ratesList = ratesSet.map {
val code = it.key
val value = it.value.asFloat
Rate(code, value)
}
return Currency(success, base, date, ratesList)
}
}
Add it to Gson like this:
val gson: Gson = GsonBuilder()
.registerTypeAdapter(Currency::class.java, CurrencyDeserializer())
.create()

Retrofit with dynamic URL

in my application am using a Retrofit 2.9.0, my issue is the user can change completely the URL from the app menu, in this case is not working when i changed the URL only if i restart the app.
this my instance of Retrofit :
object ApiService {
var token: String = ""
#JvmName("setToken1")
fun setToken(tk: String) {
token = tk
}
private val globalInterceptor = GlobalErrorInterceptor()
private val loginInterceptor = LoginErrorInterceptor()
private val okHttpClient =
OkHttpClient.Builder().addInterceptor(globalInterceptor).build()
private val okHttpClientLogin =
OkHttpClient.Builder().addInterceptor(loginInterceptor).build()
var gson = GsonBuilder()
.setLenient()
.create()
/**This instance for the others requests */
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(LOGIN_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build()
}
val API: WebServicesApi by lazy {
retrofit.create(WebServicesApi::class.java)
}
/**This instance for the login to get the Token */
private val retrofitLogin by lazy {
Retrofit.Builder()
.baseUrl(LOGIN_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClientLogin)
.build()
}
val APILogin: WebServicesApi by lazy {
retrofitLogin.create(WebServicesApi::class.java)
}
}
You can dynamically change retrofit URL by doing something like this. First change retrofit from val to var.
private fun changeBaseUrl(url: String) {
// change the base url only if new url is different than old url
if (retrofit.baseUrl().toString() != url) {
retrofit = retrofit.newBuilder().baseUrl(url).build()
}
}
Please note you might have to change this method and call it according to your flow. The main point to note here is the use of .newBuilder().baseUrl(url).build().

How do I include the DOCTYPE declaration when serializing to XML using Retrofit2 and SimpleXmlConverterFactory?

I'm trying to use SimpleXmlConverterFactory with Retrofit to create an XML request to a REST service. However, the service requires the DTD declaration in the request like so.
<!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" "http://dtd.worldpay.com/paymentService_v1.dtd">
But when SimpleXmlConverterFactory serializes the data objects, it leaves off the DOCTYPE declaration:
<paymentService merchantCode="..." version="1.4">
<inquiry>
<shopperTokenRetrieval>
<authenticatedShopperID>...</authenticatedShopperID>
</shopperTokenRetrieval>
</inquiry>
</paymentService>
Creating the SimpleXmlConverterFactory isn't anything special:
val builder = Retrofit
.Builder()
.addConverterFactory(
SimpleXmlConverterFactory.create()
)
.baseUrl(BuildConfig.WORLDPAY_BASE_URL)
.build()
.create(WorldPayApiService::class.java)
And here is the annotated data object
#Root(name = "paymentService")
data class WorldPayXmlPaymentService(
#field:Attribute(name = "version")
var version: String = "",
#field:Attribute(name = "merchantCode")
var merchantCode: String = "",
#field:Element(name = "reply", required = false)
var reply: WorldPayXmlReply? = null,
#field:Element(name = "inquiry", required = false)
var inquiry: WorldPayXmlInquiry? = null
)
I added an override of a Persister as a parameter to the SimpleXmlConverterFactory.create method, like the following. It looks for the root element and then outputs the DOCTYPE before serializing the rest.
val persister = object : Persister() {
#Throws(Exception::class)
override fun write(source: Any, out: Writer) {
(source as? WorldPayXmlPaymentService)?.let {
val docType = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE paymentService PUBLIC \"-//WorldPay//DTD WorldPay PaymentService v1//EN\" \"http://dtd.worldpay.com/paymentService_v1.dtd\">"
out.write(docType)
super.write(source, out)
}
}
}
val builder = Retrofit
.Builder()
.addConverterFactory(
SimpleXmlConverterFactory.create(persister)
)
.baseUrl(BuildConfig.WORLDPAY_BASE_URL)
.build()
.create(WorldPayApiService::class.java)
where WorldPayXmlPaymentService is my root data object.
Thanks to #CommonsWare for the guidance.

Call Retrofit2 + Decrypt + Json conversor

I am using retrofit2 in kotlin, and I need to get the content that is a json and this encrypted, I know that to convert json just use the JacksonConverterFactory (until this part was working well) but an encryption was added before that and I do not know how To handle this, do I need to create a converter of my own? Does anyone have a read to tell me?
My current call for retrofit
val retrofit = Retrofit.Builder()
.baseUrl("http://100.1.1.100/")
.addConverterFactory(JacksonConverterFactory.create())
.client(httpClient.build())
.build()
And i already have my fucntion (working) to decrypt:
CryptAES.decrypt(value))
This can be done by creating an decrypt interceptor:
class DecryptInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response = chain
.run { proceed(request()) }
.let { response ->
return#let if (response.isSuccessful) {
val body = response.body()!!
val contentType = body.contentType()
val charset = contentType?.charset() ?: Charset.defaultCharset()
val buffer = body.source().apply { request(Long.MAX_VALUE) }.buffer()
val bodyContent = buffer.clone().readString(charset)
response.newBuilder()
.body(ResponseBody.create(contentType, bodyContent.let(::decryptBody)))
.build()
} else response
}
private fun decryptBody(content: String): String {
//decryption
return content
}
}
setup:
val httpClient = OkHttpClient().newBuilder()
httpClient.addInterceptor(DecryptInterceptor())
val retrofit = Retrofit.Builder()
.baseUrl("http://100.1.1.100/")
.addConverterFactory(JacksonConverterFactory.create())
.client(httpClient.build())
.build()

Retrofit and Moshi: Parsing a JSON Object with Two Arrays

In the process of learning how to use Retrofit with Moshi to use APIs with Android, I have run into an issue I cannot get my head around. The goal here is to get a simple array of categories returned from an API. When I make the call to, in this case, the Behance API to list all creativefields, an array is not returned. Instead is is an object with two arrays:
{"fields":[{"id":108,"name":"Advertising"},{"id":3,"name":"Animation"},{"id":4,"name":"Architecture"},{"id":5,"name":"Art Direction"},{"id":130,"name":"Automotive Design"},{"id":109,"name":"Branding"},{"id":133,"name":"Calligraphy"},{"id":9,"name":"Cartooning"},{"id":124,"name":"Character Design"},{"id":12,"name":"Cinematography"},{"id":15,"name":"Computer Animation"},{"id":19,"name":"Copywriting"},{"id":20,"name":"Costume Design"},{"id":21,"name":"Crafts"},{"id":137,"name":"Creative Direction"},{"id":23,"name":"Culinary Arts"},{"id":122,"name":"Digital Art"},{"id":27,"name":"Digital Photography"},{"id":28,"name":"Directing"},{"id":110,"name":"Drawing"},{"id":31,"name":"Editing"},{"id":32,"name":"Editorial Design"},{"id":33,"name":"Engineering"},{"id":35,"name":"Entrepreneurship"},{"id":36,"name":"Exhibition Design"},{"id":37,"name":"Fashion"},{"id":93,"name":"Fashion Styling"},{"id":38,"name":"Film"},{"id":112,"name":"Fine Arts"},{"id":40,"name":"Furniture Design"},{"id":41,"name":"Game Design"},{"id":43,"name":"Graffiti"},{"id":44,"name":"Graphic Design"},{"id":131,"name":"Icon Design"},{"id":48,"name":"Illustration"},{"id":49,"name":"Industrial Design"},{"id":50,"name":"Information Architecture"},{"id":51,"name":"Interaction Design"},{"id":52,"name":"Interior Design"},{"id":53,"name":"Jewelry Design"},{"id":54,"name":"Journalism"},{"id":55,"name":"Landscape Design"},{"id":59,"name":"MakeUp Arts (MUA)"},{"id":63,"name":"Motion Graphics"},{"id":64,"name":"Music"},{"id":66,"name":"Packaging"},{"id":67,"name":"Painting"},{"id":69,"name":"Pattern Design"},{"id":70,"name":"Performing Arts"},{"id":73,"name":"Photography"},{"id":74,"name":"Photojournalism"},{"id":78,"name":"Print Design"},{"id":79,"name":"Product Design"},{"id":123,"name":"Programming"},{"id":136,"name":"Retouching"},{"id":86,"name":"Sculpting"},{"id":87,"name":"Set Design"},{"id":118,"name":"Sound Design"},{"id":91,"name":"Storyboarding"},{"id":135,"name":"Street Art"},{"id":95,"name":"Textile Design"},{"id":126,"name":"Toy Design"},{"id":97,"name":"Typography"},{"id":132,"name":"UI\/UX"},{"id":120,"name":"Visual Effects"},{"id":102,"name":"Web Design"},{"id":103,"name":"Web Development"},{"id":105,"name":"Writing"}],
"popular":[{"id":44,"name":"Graphic Design"},{"id":73,"name":"Photography"},{"id":51,"name":"Interaction Design"},{"id":5,"name":"Art Direction"},{"id":48,"name":"Illustration"},{"id":49,"name":"Industrial Design"},{"id":63,"name":"Motion Graphics"},{"id":37,"name":"Fashion"},{"id":4,"name":"Architecture"},{"id":109,"name":"Branding"},{"id":102,"name":"Web Design"},{"id":132,"name":"UI\/UX"}],"http_code":200}
How do I parse this JSON response to get two arrays of creative fields using Moshi and Retrofit? Below is the setup I had anticipated would work. Now I am aware that the JSON is not a List but more of a FieldList with 2 values of "fields" and "popular", but I can't see how to extract the arrays with Moshi.
Model of a Creative Field
data class Fields(val id: Int, val name: String)
Interface/Service
interface BehanceService{
#GET( "v2/fields")
fun creativeField(#Query("api_key") api_key: String): Call<List<Fields>>
}
The API class
object BehanceAPI {
private val BASE_URL = "http://www.behance.net/"
val retrofittedBuilder: Retrofit by lazy {
Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
val behanceService: BehanceService = retrofittedBuilder.create(BehanceService::class.java)
}
this is how your json looks like as Java Model
data class Response(
val httpCode: Int? = null,
val fields: List<FieldsItem?>? = null,
val popular: List<PopularItem?>? = null)
data class FieldsItem(
val name: String? = null,
val id: Int? = null)
data class PopularItem(
val name: String? = null,
val id: Int? = null)
Your service will be something like this:
interface BehanceService{
#GET("v2/fields")
fun creativeField(#Query("api_key") api_key: String): Call<Response>
}
And your Api class will be something like this:
object BehanceAPI {
private val BASE_URL = "http://www.behance.net/"
val retrofittedBuilder: Retrofit by lazy {
Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
val behanceService: BehanceService = retrofittedBuilder.create(BehanceService::class.java)}
you can call it in this way.
BehanceAPI.behanceService.creativeField("your_key_here").enqueue(new Call<Response>(){
#Override
public void onResponse( response: Call<Response>)
{
// Deal with the response here
val data = response.body();
}
#Override
public void onFailure(Throwable t)
{
// Deal with the error here
}})

Categories

Resources