Can you get the response data from Volley outside of the stringRequest variable in Kotlin? - kotlin

I want to get the Volley stringRequest response from my website outside of the variable.
val queue = Volley.newRequestQueue(this)
val url = ""
// Request a string response from the provided URL.
val stringRequest = StringRequest(
Request.Method.GET, url,
Response.Listener<String> { response ->
var obj = JSONObject(response) <-- cant access this variable outside of stringRequest
},
Response.ErrorListener { textView3.text = "That didn't work!" })
stringRequest.body.toString() <-- cant covert null to string
stringRequest.headers.toString() <-- output is {}
//here i want to do something like
if (response == "True") {
//do something
}
On the website that I'm accessing there is nothing more than {"check":"True"}

This implementation is asynchronous in the way its built in, What you actually can do to look it more like synchronous is if you use coroutines in your project, You can use suspendCoroutine, see
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/suspend-coroutine.html
Example:
suspend fun getData(url: String) = suspendCoroutine<String?> { cont ->
val queue = Volley.newRequestQueue(this)
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<String> { response ->
cont.resume(response)
},
Response.ErrorListener { cont.resume(null) })
queue.add(stringRequest)
}
Now you can access the response string out of Response.Listener()
Edit: Additionally you can do cont.resumeWithException(e) instead if you dont want to return nullable expression and check nullability every time you use this function.

Related

Https request POST issue in Kotlin

I'm new to Kotlin and Android developing.
I'm trying to use the HttpsURL to POST a value of '0' or any other value
to the 'data' variable https://www.mytestsite.com/on-test.php where is a 'data'
variable set inside.
My Kotlin code is reaching the '../on-php' url but the POST method is likely not executed
or no data was transferred for some unknown reason.
I also made another 'post.php' from where I'm posting to the same on-test.php like my Android app,
and that way it is working.
I have no any idea what I'm doing wrong.
Any help I would appreciate.
Here is my code I'm trying to make working:
btn_test.setOnClickListener {
object : AsyncTask<Void, Void, Void>() {
override fun doInBackground(vararg params: Void?): Void? {
val url = "https://www.mytestsite.com/on-test.php"
val data = JSONObject()
data.put("data", "0")
val connection = URL(url).openConnection() as HttpsURLConnection
connection.requestMethod = "POST"
connection.doOutput = true
connection.setRequestProperty("Content-Type", "application/json")
connection.connect()
val wr = DataOutputStream(connection.outputStream)
wr.writeBytes(data.toString())
wr.flush()
wr.close()
error_data = connection.responseMessage
return null
}
}.execute()
text_status.setText(error_data)
}

async coroutine with volley

I am trying to convert my pet app to use coroutines instead of callbacks.
I am half way, but I cannot see how to get around the callback I have in this function. Is there a way to use async to get rid of callbacks or am I climbing the wrong tree?
This is what I have so far:
const val url = "https://pokeapi.co/api/v2/pokemon/"
class PokeClient {
fun getPokemonData(context: Context, successCallBack: (Pokemon) -> Unit, pokemonName: String) = runBlocking {
val queue = Volley.newRequestQueue(context)
val request = url.plus(pokemonName)
var deferredResult = async {
val stringRequest = StringRequest(Request.Method.GET, request, Response.Listener<String> { response ->
val jObj = JSONObject(response)
val imgUrl = jObj
.getJSONObject("sprites")
.getJSONObject("other")
.getJSONObject("official-artwork")
.getString("front_default")
val inputStream = URL(imgUrl).openStream()
successCallBack(Pokemon(name = jObj.getString("name"), image = BitmapFactory.decodeStream(inputStream)))
}, Response.ErrorListener {
val toast = Toast.makeText(context, "error talking to professor Oak!", Toast.LENGTH_SHORT)
toast.show()
})
queue.add(stringRequest)
}
deferredResult.await()
}
}
Any ideas?
Thank you,
Android Newbie
Essentially you need to convert network call with callback code block into a suspending function which can be called from any coroutine, this can be done using suspendCoroutine function, it basically provides you with a continuation object, which can be used to return data from inside the Response callbacks in your case
suspend fun getPokemon() = suspendCoroutine<Pokemon> { cont ->
val queue = Volley.newRequestQueue(this)
val url = "https://pokeapi.co/api/v2/pokemon/"
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<Pokemon> { response ->
val jObj = JSONObject(response)
val imgUrl = jObj.getJSONObject("sprites")
.getJSONObject("other")
.getJSONObject("official-artwork")
.getString("front_default")
val inputStream = URL(imgUrl).openStream()
/* call continuation.resume and pass your object */
cont.resume(Pokemon(name = jObj.getString("name"), image = BitmapFactory.decodeStream(inputStream)))
},
Response.ErrorListener {
/* if network call fails then post appropriate error */
cont.resumeWithException(YourExceptoin)
})
queue.add(stringRequest)
}
Now you can call this function from a coroutine and get a Pokemon as following
runBlocking{
try { val pokeMon = getPokemon() }
catch(e: Exception) { Log.d(TAG, "Cant get pokemon") }
}
Note : its OK to use runBlocking only for learning and exploration otherwise its not a good idea, use launch or async
Edit : As noted in comment you can also use suspendCancellableCoroutine if you need to support cancellation (which you should for structured concurrency).

Spring Cloud Gateway: Post Filter Web Client Request

We are using Spring Cloud Gateway in order to route requests to multiple underlying services. The calls to these underlying services will be sequential and potentially feed into one another (response from one being used in the request for the next). We have a working solution for when we need to make those requests sequentially BEFORE the main request, but after the main request we are having problems with feeding the response of one proxy request into the request of the next.
The way we have planned on feeding the response from one request to the next is by making the request using a WebClient in the GatewayFilter and storing the response string in the exchange's attribute store. Then during the next proxy request we supply an attribute name to optionally pull the request body from. This works well when using "pre" filters, because the first proxy request is built, executed and response cached before the second request is built and executed, so the chain of attributes works as expected. The problem comes when working with "post" filters. In the post proxy, the web client requests are all built before the subsequent request has finished. So the attribute store never has the response from the previous request, meaning the next request doesn't work as intended because it doesn't have a valid request body.
My understanding was that calling chain.filter(exchange).then(Mono.fromRunnable{ ... }) would cause the .then logic to execute only after the prior filters had fully completed. This does not seem to be the case. In other filter types like logging, response manipulation, etc the post filters execute in the correct order, but when creating a WebClient they don't seem to.
Does anyone have any ideas on how this desired behavior might be achievable?
Pre-Proxy Filter Code(Working):
class PreProxyGatewayFilterFactory: AbstractGatewayFilterFactory<PreProxyGatewayFilterFactory.Params>(Params::class.java) {
override fun apply(params: Params): GatewayFilter {
return OrderedGatewayFilter(
{ exchange, chain ->
ServerWebExchangeUtils.cacheRequestBody(exchange){
val cachedExchange = exchange.mutate().request(it).build()
executeRequest(cachedExchange, params)
.map { response ->
val body = response.body.toString()
cacheResponse(
response.body.toString(),
params.cachedResponseBodyAttributeName,
cachedExchange
)
}
.flatMap(chain::filter)
}
}, params.order)
}
private fun cacheResponse(response: String, attributeName: String?, exchange: ServerWebExchange): ServerWebExchange{
if(!attributeName.isNullOrBlank()){
exchange.attributes[attributeName] = response
}
return exchange
}
private fun executeRequest(exchange: ServerWebExchange, params: Params): Mono<ResponseEntity<String>>{
val request = when(exchange.request.method){
HttpMethod.PUT -> WebClient.create().put().uri(params.proxyPath).body(createProxyRequestBody(exchange, params.cachedRequestBodyAttributeName))
HttpMethod.POST -> WebClient.create().post().uri(params.proxyPath).body(createProxyRequestBody(exchange, params.cachedRequestBodyAttributeName))
HttpMethod.GET -> WebClient.create().get().uri(params.proxyPath)
HttpMethod.DELETE -> WebClient.create().delete().uri(params.proxyPath)
else -> throw Exception("Invalid request method passed in to the proxy filter")
}
return request.headers { headers ->
headers.addAll(exchange.request.headers)
headers.remove(CONTENT_LENGTH)
}
.exchange()
.flatMap{ response ->
response.toEntity(String::class.java)
}
}
private fun createProxyRequestBody(exchange: ServerWebExchange, attributeName: String?): BodyInserter<out Flux<out Any>, ReactiveHttpOutputMessage> {
val cachedBody = attributeName?.let { attrName ->
exchange.getAttributeOrDefault<String>(attrName, "null")
} ?: "null"
return if(cachedBody != "null"){
BodyInserters.fromPublisher(Flux.just(cachedBody), String::class.java)
} else {
BodyInserters.fromDataBuffers(exchange.request.body)
}
}
data class Params(
val proxyPath: String = "",
val cachedRequestBodyAttributeName: String? = null,
val cachedResponseBodyAttributeName: String? = null,
val order: Int = 0
)
}
Post-Proxy Filter Code (Not Working)
class PostProxyGatewayFilterFactory: AbstractGatewayFilterFactory<PostProxyGatewayFilterFactory.Params>(Params::class.java) {
override fun apply(params: Params): GatewayFilter {
return OrderedGatewayFilter(
{ exchange, chain ->
ServerWebExchangeUtils.cacheRequestBody(exchange){
val cachedExchange = exchange.mutate().request(it).build()
//Currently using a cached body does not work in post proxy
chain.filter(cachedExchange).then( Mono.fromRunnable{
executeRequest(cachedExchange, params)
.map { response ->
cacheResponse(
response.body.toString(),
params.cachedResponseBodyAttributeName,
cachedExchange
)
}
.flatMap {
Mono.empty<Void>()
}
})
}
}, params.order)
}
private fun cacheResponse(response: String, attributeName: String?, exchange: ServerWebExchange): ServerWebExchange{
if(!attributeName.isNullOrBlank()){
exchange.attributes[attributeName] = response
}
return exchange
}
private fun executeRequest(exchange: ServerWebExchange, params: Params): Mono<ResponseEntity<String>>{
val request = when(exchange.request.method){
HttpMethod.PUT -> WebClient.create().put().uri(params.proxyPath).body(createProxyRequestBody(exchange, params.cachedRequestBodyAttributeName))
HttpMethod.POST -> WebClient.create().post().uri(params.proxyPath).body(createProxyRequestBody(exchange, params.cachedRequestBodyAttributeName))
HttpMethod.GET -> WebClient.create().get().uri(params.proxyPath)
HttpMethod.DELETE -> WebClient.create().delete().uri(params.proxyPath)
else -> throw Exception("Invalid request method passed in to the proxy filter")
}
return request.headers { headers ->
headers.addAll(exchange.request.headers)
headers.remove(CONTENT_LENGTH)
}
.exchange()
.flatMap{ response ->
response.toEntity(String::class.java)
}
}
private fun createProxyRequestBody(exchange: ServerWebExchange, attributeName: String?): BodyInserter<out Flux<out Any>, ReactiveHttpOutputMessage> {
val cachedBody = attributeName?.let { attrName ->
exchange.getAttributeOrDefault<String>(attrName, "null")
} ?: "null"
return if(cachedBody != "null"){
BodyInserters.fromPublisher(Flux.just(cachedBody), String::class.java)
} else {
BodyInserters.fromDataBuffers(exchange.request.body)
}
}
data class Params(
val proxyPath: String = "",
val cachedRequestBodyAttributeName: String? = null,
val cachedResponseBodyAttributeName: String? = null,
val order: Int = 0
)
}
Was finally able to get to a working solution for the post filter proxy pulling it's request body from the attributes. It was a relatively straightforward fix that I just couldn't find the answer to. Instead of using chain.filter(exchange).then(Mono.fromRunnable { ...execute proxy request...}) I just needed to use chain.filter(exchange).then(Mono.defer { ...execute proxy request...}).

Kotlin - chain of scope functions

I'm new to Kotlin language and I would like to know if it's a good practice have a chain of scope functions. As example, I'm writing a function that calls some API (an utilitary function), parse the string response to specific object, do a small verification and return an object.
Is a good practice have a chain of scope functions like this code above?
fun execRequest(endpoint: String, method: String = "GET", body: String? = ""): String =
defaultHttpRequestBuilder()
.uri(URI.create(endpoint))
.method(method, HttpRequest.BodyPublishers.ofString(body))
.header("Content-Type", "application/x-www-form-urlencoded")
.build()
.run { httpClient.send(this, HttpResponse.BodyHandlers.ofString()) }
.let { it.body() }
fun processLoginRequest(challenge: String) =
execRequest(buildEndpoint("login", challenge))
.let {
mapper.readValue<LoginResponse>(it)
}
.let {
val authSituation = Auth(it.skip, it.challenge)
if (it.skip) {
val acceptResponse = acceptLoginRequest(challenge, it.subject)
authSituation.redirectTo = acceptResponse.redirectTo
}
authSituation
}
This code looks awful in my opinion. Is there another way to write it in a "Kotlin way"?
I think this question is likely to be closed due to answers being a matter of opinion, but since you asked about my comment, here is how you could break up the first function.
fun execRequest(endpoint: String, method: String = "GET", body: String? = ""): String {
val request = defaultHttpRequestBuilder()
.uri(URI.create(endpoint))
.method(method, HttpRequest.BodyPublishers.ofString(body))
.header("Content-Type", "application/x-www-form-urlencoded")
.build()
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
return response.body()
}
I might break up the second function like this
fun processLoginRequest(challenge: String): Auth {
val httpResponse = execRequest(buildEndpoint("login", challenge))
val loginResponse: LoginResponse = mapper.readValue(httpResponse)
return Auth(loginResponse.skip, loginResponse.challenge)
.apply {
if (loginResponse.skip)
redirectTo = acceptLoginRequest(challenge, it.subject).redirectTo
}
}

Vertx/RxJava/Retrofit blocking

I'm working on a Kotlin/Vertx/RxJava/Retrofit server, but some calls are blocking Vertx when calling an external API that takes too long.
The original handler makes the call:
val response = weatherService.getWeatherSummaryByCity(countryCode = queryParams[0], adminCode = queryParams[1], cityName = queryParams[2])
This in turn executes the external call:
fun getWeatherSummaryByCity(countryCode: String, adminCode: String, cityName: String): WeatherSummary? {
val citiesList = externalAPI.getLocationByCityName(countryCode = countryCode, adminCode = adminCode, cityName = cityName)
var weatherSummary : WeatherSummary? = null
citiesList
.doOnError { error -> print(error) }
.filter { cityList -> !cityList.isEmpty() }
.map { cityList -> cityList[0] }
.filter { city -> city.Key != null && !city.Key.isEmpty() }
.subscribe( { city: City -> weatherSummary = createWeatherSummary(city) } )
return weatherSummary
}
And here's the interface used by Retrofit
interface ExternalAPI {
#GET("/locations/{version}/cities/{countryCode}/{adminCode}/search.json")
fun getLocationByCityName(
#Path("version") version: String = "v1",
#Path("countryCode") countryCode: String,
#Path("adminCode") adminCode: String,
#Query("q") cityName: String,
#Query("apikey") apiKey: String = key,
#Query("details") details: String = "true",
#Query("language") language: String = "en-US"): Observable<List<City>>
}
The code as is works, but if the externalAPI takes too long it blocks Vertx. Same happens when I try this:
Json.encodePrettily(response)
and the response is too big. Any ideas to avoid the blocking?
I see two ways to handle your problem:
use an async http client to fetch the getLocationByCityName. I am not using retrofit, but looking at this: https://futurestud.io/tutorials/retrofit-synchronous-and-asynchronous-requests it has out of the box support for it.
Blocking code can always be executed on a dedicated worker thread by calling vertx.executeblocking. This can be read up here: https://vertx.io/docs/vertx-core/java/#blocking_code
I would suggest option 1 as it is more clean.