I am trying to GET request for the API but a special character "*" (asterisk sign) breaks my API call and hence it is sent incomplete. Is there any way to escape it?
Instead of this:
https://rest2.bermanngps.cl/BermannRest/api/enviacomando?tk=3cf40d35c4e48b60e007cdc85f1342f5&comando=%24SRVFFFFFF%2C25%2C1*8F&md5pass=4e1ed8ef96fb83a0a30c39b0019fadc7&user=1017&avserie=12977
this is sent:
https://rest2.bermanngps.cl/BermannRest/api/enviacomando?tk=3cf40d35c4e48b60e007cdc85f1342f5&comando=%24SRVFFFFFF%2C25%2C1
I am using the GET request method of retrofit, using the repository to load Query strings dynamically. How would I use the URLEncoder method there?
#GET("enviacomando")
suspend fun getSendComando(
#Query("tk") tk: String,
#Query("comando") comando: String,
#Query("md5pass") md5pass: String,
#Query("user") user: String,
#Query("avserie") avserie: String
): Response<SendComandoResponse>
You can use URLEncoder class for this.
So you need to encode query part from URL with URLEncoder.
String query = URLEncoder.encode("tk=3cf40d35c4e48b60e007cdc85f1342f5&comando=%24SRVFFFFFF%2C25%2C1*8F&md5pass=4e1ed8ef96fb83a0a30c39b0019fadc7&user=1017&avserie=12977", "utf-8");
String url = "https://rest2.bermanngps.cl/BermannRest/api/enviacomando?" + query;
I just changed the conflicting value with the URLEncoder.
token?.let { tk ->
userId?.let { user ->
comando?.comandoComando?.let { it1 ->
mVehiculo?.let { it2 ->
mViewModel.sendComando(
tk = tk,
avserie = it2.avSerie,
user = user,
comando = URLEncoder.encode(it1, "UTF-8"),
md5pass = pass
)
}
}
}
}
Since this method didn't work consistently I tried using #Url to send the request.
Still, many times the request breaks at the "*" (asterisk sign)
Related
I am calling a post API with some parameters. one the parameters is
activatecode="aaaaaa$rr"
when the API call is made, it is sent as
activatecode=aaaaaa%24rr
The $ is encoded as %24. How to avoid this and send the special character as it is?
I am using Retrofit 2.9.0.
I have this service :
interface WordpressService {
#GET("wp-json/wp/v2/posts")
suspend fun getPosts(
#Query("page") page: Int,
#Query("per_page") limit: Int,
#Query("_fields", encoded = true) fields: String = "date,link,title,content,excerpt,author"
): List<Post>
}
Without putting encode = true, I end up with this request :
GET http://example.org/wp-json/wp/v2/posts?page=1&per_page=10&_fields=date%2Clink%2Ctitle%2Ccontent%2Cexcerpt%2Cauthor
With encode = true, I get :
GET http://motspirituel.org/wp-json/wp/v2/posts?page=1&per_page=10&_fields=date,link,title,content,excerpt,author
So in my case, adding encode = true in annotation solved my problem.
So, I have given an API that returns this on a POST call
{
"JHK":"One piece",
"LKJ":"Two pieces",
"OEN":"Three pieces"
}
And since this is a list but is not good formated from the backend I dont know how to get it from Android, this is what I have tried
WebService
#POST("get_user_codes.php")
suspend fun getUserCodesByMemberId(#Body member_id:String): CodesResponse
CodesResponse
data class CodesResponse(val data: List<Code>)
data class Code(val code_id:String,val name:String)
Repository
suspend fun getUserCodes(memberId:String):Result<CodesResponse>{
return Result.Success(webService.getUserCodesByMemberId(memberId))
}
But his is outping Null as the response, I really dont know how to bring those different objects that are supposed to be an array of object but instead they are all inside one object
Any idea ?
API INPUTS
member_id as text string
Example of input:
{ "member_id": "1"}
API OUTPUTS
code_id: as a String
name: as a String
{
"JHK":"One piece",
"LKJ":"Two pieces",
"OEN":"Three pieces"
}
EDIT
the values can be more than those 3 I posted, it depends on how many the response returns
suppose you have response like that
String json = {
"JHK":"One piece",
"LKJ":"Two pieces",
"OEN":"Three pieces"
}
then you can get a list of values ignoring keys like:
ArrayList<String> arr = new ArrayList<>();
try {
JSONObject response = new JSONObject(json);
Iterator keys = response.keys();
while (keys.hasNext()) {
// loop to get the dynamic key
String currentKey = (String) keys.next();
// get the value of the dynamic key
String value = response.getJSONObject(currentKey).toString();
arr.add(value);
}
} catch (Throwable t) {
Log.e("My App", "Could not parse malformed JSON: \"" + json + "\"");
}
Are field names JHK LKJ OEN always the same? You said there can be more than 3, what will be the other name when it's more than 3?
AbdelraZek posted a great solution to this problem: https://stackoverflow.com/a/64246981/14259754
My version of it in Kotlin with Retrofit in mind:
Retrofit:
// Here we use ScalarsConverter to be able to return String as a response.
Retrofit.Builder()
.baseUrl("http://YourURL")
.addConverterFactory(ScalarsConverterFactory.create())
.build()
.create(YourInterfaceClass::class.java)
// Then you can proceed to your POST function
suspend fun getUserCodesByMemberId(#Body member_id:String): String
// I suggest using Response<String> so you can check the response code and process it if needed.
Then, wherever you need, just do the following:
val response = getUserCodesByMemberId
val json = JSONObject(response.body()!!)
val array = mutableListOf<String>()
val keys: Iterator<String> = json.keys()
while (keys.hasNext()) {
val key = keys.next()
array.add(json[key].toString())
}
This way you can work with Json response that is unknown to you.
It will return null because the JSON inside has different keys("JHK", "LKJ" etc).
As Retrofit uses GSON, you need to create a variable with the same name as JSON keys. You will have to use JSONObject and parse the response.
Instead of using
#POST("get_user_codes.php")
suspend fun getUserCodesByMemberId(#Body member_id:String): CodesResponse
use
#POST("get_user_codes.php")
suspend fun getUserCodesByMemberId(#Body member_id:String): JSONObject
I use the Apollo Android library to make queries to a GraphQL endpoint.
Everything works OK until I try to convert the results back to JSON strings (to store them in a Room database). I naively tried to use Moshi, however this fails with the following error:
Cannot get available extra products: No JsonAdapter for interface com.example.MyQuery$MyFragmentInterface
where MyFragmentInterface in an interface generated by Apollo to handle query fragments.
So, I tried to find whether the Apollo library has/generates any conversion methods, i.e. sth like toJson()/fromJson(), for the generated models, however I couldn't find anything usable.
Am I missing something obvious?
Since Apollo 1.3.x there is an Operation.Data.toJson() extension function (Kotlin) and a corresponding serialize static method in Java.
Check https://www.apollographql.com/docs/android/advanced/no-runtime/#converting-querydata-back-to-json
For the opposite, Json string to Query.Data, I use something like the following:
fun String?.toMyData(): MyQuery.Data? {
this ?: return null
val query = MyQuery()
val response: Response<MyQuery.Data> =
OperationResponseParser(query,
query.responseFieldMapper(),
ScalarTypeAdapters.DEFAULT)
.parse(Buffer().writeUtf8(this))
return response.data
}
Here is an example how you can deserialize string as generated object. Inlined comments for further details.
// Sample string response
val jsonResponse = """
{
"data": {
{
"id": "isbn-889-333",
"title": "Awesome title",
"rating": 5
}
}
""".trimIndent()
// Create query that is expect to return above response
val query = QueryBookDetails("book_id")
// convert string to input stream
val inputStream = ByteArrayInputStream(jsonResponse.toByteArray())
// Read bytes into okio buffer
val buffer = Buffer().readFrom(inputStream)
// Use the query to parse the buffer.
val response = query.parse(buffer)
// response.data might be the one needed by you
Bear in mind that the response must honour the schema.
Try Postman and see if the error also appears there https://blog.getpostman.com/2019/06/18/postman-v7-2-supports-graphql/
As I am newbie in Android, I am building an application in which I need to update layer data on Geo server. For that scenario, I m calling post method along with XML body request.
I need to send data in xml format in request body and need to get response in xml. For that, I tried using XmlSerializer instead of JsonFeature but I got error saying "XmlSerializer is not comapanion object, need to initialize here"
val httpClient = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer(Json.nonstrict)
}
}
Thanks in Advance!!
There are no XML related features, but you can use existing Json with the XML serializer
The first thing you need to do is to find an appropriate serializer. I used https://github.com/pdvrieze/xmlutil.
implementation("net.devrieze:xmlutil-android:0.20.0.1")
You also can use other platform dependencies(I used the JVM and multiplatform).
Next you can configure the JsonFeature feature with a custom serializer:
val client = HttpClient {
Json {
serializer = XMLSerializer()
accept(ContentType.Application.Xml)
}
}
Using ContentType.Application.Xml
And finally, add the serializer:
#OptIn(ImplicitReflectionSerializer::class)
class XMLSerializer(private val format: XML = XML()) : JsonSerializer {
override fun read(type: TypeInfo, body: Input): Any {
val text = body.readText()
val deserializationStrategy = format.context.getContextual(type.type)
val mapper = deserializationStrategy
?: type.kotlinType?.let { serializer(it) }
?: type.type.serializer()
return format.parse(mapper, text) ?: error("Failed to parse response of type $type. The result is null.")
}
override fun write(data: Any, contentType: ContentType): OutgoingContent {
val serializer = data::class.serializer() as KSerializer<Any>
val text = format.stringify(serializer, data, null)
return TextContent(text, contentType)
}
}
Here is the full result sample with the server(adopted to run without Android): https://gist.github.com/e5l/3b4d5d704b4d7c6e2a65cf68de8e9ca4
I am trying to make use of the twitter api, and am setting up a handler to deal with twitter api requests.
To do this I am using an HTTPUrlConnection and following the Twitter api docs
I've managed to get authenticated using the 3-legged OAuth process, but am stuck when actually trying to make requests with the oauth token.
Here is an example of what my auth headers look like:
Accept=*/*,
Connection=close,
User-Agent=OAuth gem v0.4.4,
Content-Type=application/x-www-form-urlencoded,
Authorization=
OAuth oauth_consumer_key=****&
oauth_nonce=bbmthpoiuq&
oauth_signature=*****%3D&
oauth_signature_method=HMAC-SHA1&
oauth_timestamp=1570586135&
oauth_token=*****&
oauth_version=1.0,
Host=api.twitter.com
for each header in the auth header I add it to my HTTP GET call like this:
urlConnection.setRequestProperty(header.key, header.value)
Note that Authorization points to one string
OAuth oauth_consumer_key=****&oauth_nonce=bbmthpoiuq&oauth_signature=*****%3Doauth_signature_method=HMAC-SHA1oauth_timestamp=1570586135&oauth_token=*****&oauth_version=1.0,Host=api.twitter.com
The following params are collected as follows:
oauth_consumer_key is my application API key
oauth_signature is computed by the hmacSign function below
oauth_token is the "oauth_token" received in the response from /oauth/access_token
The hmacSign function:
private fun hmacSign(requestType: RequestType, url: String, params: Map<String, String>): String {
val type = "HmacSHA1"
val key = "$API_SECRET&$tokenSecret"
val value = makeURLSafe("${requestType.string}&$url${getURLString(params.toList().sortedBy { it.first }.toMap())}")
val mac = javax.crypto.Mac.getInstance(type)
val secret = javax.crypto.spec.SecretKeySpec(key.toByteArray(), type)
mac.init(secret)
val digest = mac.doFinal(value.toByteArray())
return makeURLSafe(Base64.encodeToString(digest, NO_WRAP))
}
private fun makeURLSafe(url: String) : String {
return url.replace("/", "%2F")
.replace(",", "%2C")
.replace("=", "%3D")
.replace(":", "%3A")
.replace(" ", "%2520")
}
protected fun getURLString(params: Map<String, Any>) : String {
if (params.isEmpty()) return ""
return params.toList().fold("?") { total, current ->
var updated = total
updated += if (total == "?")
"${current.first}=${current.second}"
else
"&${current.first}=${current.second}"
updated
}
}
In the GET call I'm referring to, tokenSecret would be the oauth secret received from /oauth/access_token
After i make the call I get a 400: Bad Request
Is there anything obvious I'm doing wrong?
Update: By putting the params at the end of the url like ?key=value&key2=value2... instead of a 400 I get a 401. So I'm not sure which is worse, or which is the right way to do it.
Okay, finally got it working
So using the suggestion in the comments, I downloaded postman and copied all my info into postman - when i made the request there, I got a 200!
So then i looked back and tried to figure out what was different and there were a few things:
First the hmac sign function was missing an &
New function (added another & after the url):
private fun hmacSign(requestType: RequestType, url: String, params: Map<String, String>): String {
val type = "HmacSHA1"
val key = "$API_SECRET&$tokenSecret"
val value = makeURLSafe("${requestType.string}&$url&${getURLString(params.toList().sortedBy { it.first }.toMap())}")
val mac = javax.crypto.Mac.getInstance(type)
val secret = javax.crypto.spec.SecretKeySpec(key.toByteArray(), type)
mac.init(secret)
val digest = mac.doFinal(value.toByteArray())
return makeURLSafe(Base64.encodeToString(digest, NO_WRAP))
}
Next I noticed my auth header had its params seperated with & but they all should've been replaced with , - i also needed to surround each of my values in ""
So it looked like:
OAuth oauth_consumer_key="****",oauth_nonce="bbmthpoiuq",oauth_signature="*****%3D",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1570586135",oauth_token="*****",oauth_version="1.0",Host="api.twitter.com"
After these changes i started getting 200!
Hopefully this helps someone else (though im sure its unlikely considering how specific these issues were)