how to properly use OKHttp FormBody.Builder() with Kotlin? - kotlin

I am trying to access the DVLA's Vehicle Enquiry API. Ive got this working with a really simple example in Python, but I really want to get this working in Kotlin using OKHttp so i can use the code in an Android app.
There seems to be some issue i cant figure out with the FormBody.Builder() and the vehicle registration that i put in the body. The error im getting from the API is...
{"errors":[{"status":"400","code":"ENQ108","detail":"Invalid format for field - vehicle registration number","title":"Bad Request"}]}
The DVLA's website shows a cUrl request example, I converted that to Python and it works perfectly, but i cant figure out what im missing when doing it with OKHttp/Kotlin.
You'll see in the Kotlin code i added a println(request) just so i could see what was being sent to the api. that request when printed contains the url + header, but NO body!
Kotlin Code - not working
import okhttp3.*
import java.io.IOException
private val client = OkHttpClient()
private val url_dvla = "https://driver-vehicle-licensing.api.gov.uk/vehicle-enquiry/v1/vehicles"
private val auth_dvla = "MY_API_KEY"
fun main(){
run(url_dvla)
}
fun run(url: String){
val requestBody = FormBody.Builder()
.add("registrationNumber", "AP08EKT")
.build()
val request = Request.Builder()
.url(url)
.post(requestBody)
.addHeader("x-api-key", auth_dvla)
.build()
println(request)
client.newCall(request).enqueue(object : Callback{
override fun onFailure(call: Call, e: IOException) {
TODO("Not yet implemented")
}
override fun onResponse(call: Call, response: Response) {
println(response.body?.string())
}
})
}

Related

how can I get GeoJson data from a url in Kotlin

I am a beginner in kotlin and I would like to have the information contained in a url:
in my case I have a list of url, each url contains information formatted in geojson and I would like to get this information.
Does anyone have an idea of how I can do this?
here is an example of the url that can be found in my list :
https://database-geojsons-test.s3.amazonaws.com/COOPERATIVES/Maps%2520test/Exploitation/Alpha/Alpha.geojson
you can use Retrofit and add a Gson converterFactory. You can read more about it on the android developer web site.
Here is just an exemple:
private val BASE_URL : String = "http [your URL]"
val interceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY
}
val client: OkHttpClient = OkHttpClient.Builder().apply {
this.addInterceptor(interceptor)
}.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create()) //Converters can be added to support other types in body
.build()
I hope that works for you :)
It's quite simple as I use Retrofit I created a method that will retrieve the json content
#GET
suspend fun getGeoJsonData(#Url url: String) : String
and I could retrieve the content by calling this method for each json

kotlin: retrofit2 getting 404 url not found error

Getting Response{protocol=http/1.1, code=404, message=Not Found, url=https://test.test.com/service/one}
The url is correct as postman works fine.
I have tried looking into this error but most things come back with URL was in correct. and the error itself is vague.
code that starts it. the builder is a json string that is valid. I have tested it in postman.
CoroutineScope(Dispatchers.Default).launch {
val call = submitService.submitCarton(builder.toString())
Log.d("submit", "begining")
withContext(Dispatchers.Main) {
if (call.isSuccessful) {
Log.d("submit",call.body() as String)
} else {
Log.d("submit", "else....")
}
}
}
service factory:
fun makeSubmitService() : SubmitService{
val url = "https://test.test.com/service/"
return Retrofit.Builder().baseUrl(url)
.client(okHttpClient).addConverterFactory(GsonConverterFactory.create())
.build().create(SubmitService::class.java)
}
interface:
interface SubmitService {
#POST("one")
suspend fun submitCarton(#Body json: String): Response<myModel>
}
Expected results are a json response however I am not getting that far.
edit: I created a okhttpclient and did a request manual and I get a message 200 ok.
code for my test
val JSON = MediaType.parse("application/json; charset=utf-8")
val client = OkHttpClient()
val body = "some json"
val requestBody = RequestBody.create(JSON, body)
val request = Request.Builder()
.url("https://test.test.com/service/one")
.post(requestBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(request: Request, e: IOException) {
Log.e("test", e.toString())
}
#Throws(IOException::class)
override fun onResponse(response: Response) {
Log.d("test", response.toString())
}
})
Solved it myself.
Issue was dumb, retrofit2 was giving 404 even though the web service was returning a error message.
added
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.1'
private val interceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
private val okHttpClient = OkHttpClient().newBuilder()
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.build()
found out retrofit was sending a very unformatted string
"{ \"all my json filled with \" }"
instead of
{ json }
fixed it by adding
.addConverterFactory(ScalarsConverterFactory.create())
to my service factory
for anyone wondering why I am basically creating the json as a string instead of using a JSON object is because the service I talk to really really wants it to be in a very specific order which JSON just don't care about it however it wants it to look like JSON as well...

API call and coroutines issues

I have an issue with Kotlin and coroutines. I've written this method to call an api and get some information from this. I call calculateDistance, which works out the distances for me in KM. The issue that I have is that the UI loads before this method is completed. I've been trying to work out a way around this using coroutines, however, I seem to be coming stuck.
This function returned String is then used to render in an Activity.
Thanks
private fun getPostcodeLocation(listOfPostCodes: List<Pair<Boolean, String>>): String {
val jsonList = JSONObject()
jsonList.put("postcodes", JSONArray(listOfPostCodes.map { it.second}))
val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonList.toString())
val request = okhttp3.Request.Builder().url("https://api.postcodes.io/postcodes" ).post(body).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call, response: Response) {
val responseBody = response.body()?.string()
val jsonTree = JsonParser().parse(responseBody).asJsonObject
val resultJsonObj = jsonTree.asJsonObject.getAsJsonArray("result").asJsonArray.iterator()
resultJsonObj.forEach {
val resultObject = Gson().toJsonTree(it).asJsonObject.getAsJsonObject("result")
val lat = resultObject.get("latitude").asDouble
val lon = resultObject.get("longitude").asDouble
listOfLonLat.add(PostcodeLongLat(lon, lat))
}
distance = calculateDistance(listOfLonLat)
Log.d("PostCodeChecker", "This is the distance inside the callback $distance")
}
override fun onFailure(call: Call, e: IOException) {
println(call)
println(e)
Log.d("PostCodeChecker", "Failed to get the information from the API")
distance = "Not Available"
}
})
Log.d("PostCodeChecker", "This is the distance $distance")
return distance
}
Expected a string representing the distance between two long/lat points on a map, get Null.
this doesn't seem like a coroutines-related question. Your api call is happening asynchronously, hence you can't expect to get the result before returning distance. What you can do is access the UI element you need in onResponse method. E.g.
override fun onResponse(call: Call, response: Response) {
//parsing, calculations etc.
yourTextView.text = distance.toString
}

How to understand this snippet of Kotlin code?

I come from Java and I'm following a tutorial online regarding using the Volley library to make web requests in Android.
The instructor created the request variable like this:
val registerRequest = object : StringRequest(Method.POST, URL_REGISTER, Response.Listener {
println(it) // will print the response
complete(true)
}, Response.ErrorListener {
Log.d("ERROR", "Could not register user: $it")
complete(false)
}) {
override fun getBodyContentType(): String {
return "application/json; charset=utf-8"
}
override fun getBody(): ByteArray {
return requestBody.toByteArray()
}
}
I understand that he's creating a registerRequest variable of type StringRequest. But what I don't understand is why he prefixed StringRequest with object : here.
Also I understand that StringRequest constructor takes in an Int, String, Lambda, Lambda. After that it becomes confusing to me because the developer was able to declare some override methods after the constructor closes. Why did they do this? From what I can tell, this is similar to subclassing StringRequest, then writing the override methods there? Am I right?
Coming from Java, this way of writing code is quite unusual to me.

RxJava Zip not working properly

I am trying to implement this complex API structure.
So I tried to implement it with RxJava2 zip for parallel requests
private fun getDetails(marketDataQuotes: MarketDataQuotes, instrumentById: InstrumentById, subscribe: Subscribe): Observable<DetailsWatchListModel> {
return Observable.zip(
getMarketDataQutoes(marketDataQuotes),
getInstrumentById(instrumentById),
getSubscribeInstrument(subscribe),
Function3<MarketDataQuotesResponse, List<InstrumentByIdResponse>, SubscribeResult,DetailsWatchListModel>
{ marketData, instrumentList, subscribeInstrument ->
detailWatchList(marketData, instrumentList, subscribeInstrument)
})
}
but facing this Issue
private fun getSubscribeInstrument(subscribe: Subscribe): LiveData<SubscribeResult> {
val mutableLiveData = MutableLiveData<SubscribeResult>()
remoteServices.requestSubscribe(subscribe)
.subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : ErrorCallBack<BaseResponse<SubscribeResult>>() {
override fun onSuccess(t: BaseResponse<SubscribeResult>) {
L.d("Success of Market data Quotes")
// mutableLiveData.value = transform(t)
}
})
return mutableLiveData
}
And other API calls are like this with single place error handling and base Response Structre
And Service like
#Headers("Content-Type: application/json")
#POST("instruments/subscription")
fun requestSubscribe(#Body subscribe: Subscribe): Observable<BaseResponse<SubscribeResult>>
Using Kotlin v1.2.21 , retofit 2.3.0 , RxJava2 2.1.5 Please let me know what i am doing wrong.. Tanx in Advance
LiveData cannot be zipped with RxJava.
You need to use this API: https://developer.android.com/reference/android/arch/lifecycle/LiveDataReactiveStreams.html
So convert your LiveData to RxJava and then zip it.