I'm developing an Android app using Retrofit to connect to a Spring Boot server.
When I update data, there are extra slashes and double quotes on the server.
This is the output of POST method. "open"
This is the output of PUT method. "\"open\""
I read a similar article and I'm guessing I encode twice, but I don't know where I'm doing it. Please help me.
This is the service class of Android.
#PUT("/posts/close/update/{id}")
fun updateClose(#Path("id") id: Long, #Body close: String): Call<ResponseBody>
This is the view.
onClick = {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://*****.com")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
thread {
try {
val service: PostService =
retrofit.create(PostService::class.java)
service.updateClose(6, "open")
.enqueue(object : Callback<ResponseBody> {
override fun onResponse(
call: Call<ResponseBody>,
response: Response<ResponseBody>
) {
Log.d("Response is", "${response.body()}")
}
override fun onFailure(
call: Call<ResponseBody>,
t: Throwable
) {
Log.d("Hi", "error")
}
})
} catch (e: Exception) {
Log.d("response-weather", "debug $e")
}
}
This is the repository of Spring Boot.
#Modifying
#Transactional
#Query("UPDATE posts SET close = :close where post_id = :id", nativeQuery = true)
fun updateClose(#Param("id") id: Long, #Param("close") close: String)
Thank you very much.
There is nothing wrong with the data or the android side.
Strings in JSON must be written in double quotes. For more info refer this page.
Your JSON data is {"name": "Ken", "uid": "12345"}
In order to use double quotes inside a string you have to escape it via a backslash. For more info refer this question.
That's the reason for the extra backslashes.
I tried to load the json string via python and it worked like a charm. Attaching screenshot for reference. So any backend you would be using will be able to parse the JSON String.
Finally, I got the codes which work fine.
Service Class of Android.
#PUT("/posts/close/update/{id}")
fun updateClose(#Path("id") id: Long, #Query("close") close: String): Call<ResponseBody>
Controller class of Spring Boot. Before, I used #RequestBody instead of #RequestParam.
#PutMapping("/posts/close/update/{id}")
fun updateClose(#PathVariable id: Long, #RequestParam close: String) = postService.updateClose(id, close)
Related
I want to create a REST-API between an Android client and a Spring Boot server.
I created an OpenAPI 3.0 specification and used the CLI generator from https://openapi-generator.tech to create client and server stubs.
The server part works as intended when accessing it with other clients.
For the client side I used the generator for Kotlin with Retrofit2 via the parameter --additional-properties=library=jvm-retrofit2.
What I get is:
A ModelApi interface, defining my endpoint
A Model class, containing my model
An infrastructure package, containing ApiClient, ResponseExt, Serializer, CollectionFormats and a few *Adapter classes
The generated model class (shortened):
data class MapModel (
#Json(name = "id")
val id: kotlin.Long? = null,
#Json(name = "description")
val desc: String? = null
)
The API interface:
interface MapModelApi {
#GET("mapModel")
fun mapModelGet(): Call<kotlin.collections.List<MapModel>>
#DELETE("mapModel/{mapModelId}")
fun mapModelMapModelIdDelete(#Path("mapModelId") mapModelId: kotlin.Int): Call<Unit>
#GET("mapModel/{mapModelId}")
fun mapModelMapModelIdGet(#Path("mapModelId") mapModelId: kotlin.Int): Call<MapModel>
#PUT("mapModel/{mapModelId}")
fun mapModelMapModelIdPut(#Path("mapModelId") mapModelId: kotlin.Int, #Body mapModel: MapModel): Call<Unit>
#POST("mapModel")
fun mapModelPost(#Body mapModel: MapModel): Call<Unit>
#PUT("mapModel")
fun mapModelPut(#Body mapModel: MapModel): Call<Unit>
}
To do a GET request on the element 0, i tried this in my Activity:
val apiClient = ApiClient()
val mapObjectService = apiClient.createService(MapModelApi::class.java)
val call = mapObjectService.mapModelMapModelIdGet(0)
call.enqueue(object : Callback<MapModel> {
override fun onFailure(
call: Call<MapModel>,
t: Throwable
) {
Log.v("retrofit", "call failed")
t.printStackTrace()
}
override fun onResponse(
call: Call<MapModel>,
response: Response<MapModel>
) {
if (response.isSuccessful) {
val mapModel = response.body()
println(mapModel?.id)
} else {
val statusCode = response.code()
println("Http Code: $statusCode")
}
}
})
When I execute this I get a response, but it is always a 501 response "Not Implemented".
How can I fix this? What is missing in the code?
The server is the problem. The GET request returned a body with example data. I have overseen, that the request code sent by the server was not 200, but 501.
I'm sending post request to node.js server with image and user but user body is showing empty in node server.
Node server is using multer to parsing file.
here is my Api.kt interface file
#Multipart
#POST("/user/upload_avatar")
fun uploadAvatarImage(
#Part image:MultipartBody.Part,
#Part("user") user: RequestBody
):Call<UploadImageResponse>
MainActivity.kt file
val user:String = """{"_id":"61db06b6e488c5b13211111","username":"abcda"}"""
val multipart =MultipartBody.Part.createFormData("file",file.name,avatar)
ServiceBuilder.buildService(Api::class.java).uploadAvatarImage(
multipart,
RequestBody.create(MediaType.parse("application/json"), user)
).enqueue(object :Callback<UploadImageResponse>{
override fun onResponse(
call: Call<UploadImageResponse>,
response: Response<UploadImageResponse>
) {
Toast.makeText(this#UploadImages, "Image upload successfully", Toast.LENGTH_SHORT).show()
}
override fun onFailure(call: Call<UploadImageResponse>, t: Throwable) {
Toast.makeText(this#UploadImages, "Wrong With Image", Toast.LENGTH_SHORT).show()
}
})
First why not, making your life easier and just use ktor. With ktor you also don't have to write as much bulk code you have to write when you use a Java Libaray.
Usage example: https://medium.com/#shrikantjagtap99/uploading-multipart-form-data-using-ktor-http-client-bc3e1c6c2ce8
Kotlin is interoperable with Java, but they still have big differences in how things are done. e.g. callbacks ;)
If you still want to use retrofit:
According to: POST Multipart Form Data using Retrofit 2.0 including image
You have to use RequestBody for the image too. Maybe this solves you problem.
Guess, I'm already baffled with what I'm doing, so I'm reaching out to the community.
I have the following:
UserModel
data class UserModel(
val id: String,
val name: String
){}
UserService
#Headers("Accept: application/json")
#POST("register")
fun doRegisterUserTest(
#Query("user_id") userName: String,
#Query("password") passWord: String
): Deferred<UserModel>
UserRepository
fun test(username: String, password: String): List<UserModel>{
Network.createNetworkRequest().create(UserService::class.java).doRegisterUserTest(username, password)
val x: UserService by lazy {Network.createNetworkRequest().create(UserService::class.java)}
val y = x.doRegisterUserTest(username, password)
return y
}
UserViewModel
private val _result = MutableLiveData<List<UserModel>>()
val result: LiveData<List<UserModel>> = _result
fun onRegister(username: String, password: String) {
viewModelScope.launch {
// connect to api server
//_registrationStatus.value = RegistrationStatus.LOADING
try {
_result.value = userRepository.test(username, password)
....
}
}
}
Network Client
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
/**
* Main entry point for dto access. Call like `Network.devbytes.getPlaylist()`
*/
object Network {
fun createNetworkRequest(): Retrofit {
// Configure retrofit to parse JSON and use coroutines
val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2:8081/api/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
return retrofit
}
}
In my api, the above (in ideal scenario) will yield the following:
{
"user": [
{
"user_sys_id": 0,
"name": "Blah blah",
}
]
}
I want to read the values from _result.value = userRepository.test(username, password), I presume that test function will return a List> but I don't know how to access the members.
How can I check _result for the values of user_sys_id and name?
How come I'm getting the following (if I convert the response straight to a List)
Unable to create call adapter for Xmodel for method UserService.doRegisterUserTest
Am I missing something?
Likewise, is it fine to do a Retrofit Call if you were already using Coroutines?
From this Unable to create call adapter for retrofit2.Response<...>, it seems that the function should be suspended, but if I do that I would get a warning
Redundant suspend modifier
PS. Although, if I follow repository pattern, I'm able to see that results where written on a local database the values retrieved from the API, however, I don't think that is correct to store data that aren't supposed to be stored in the first place, say for eg. in a Registration, if the registration fails you just want to read the error message directly from the response
I am sending my token as Authorization in Retrofit but I always get a 401 code. But if I use the same token in Postman, I can get access. I know I am able to access the webapi because I can Login just fine and able to get the token from the Web Api. Please see my code below:
ApiService Interface
#POST("consolidated/sample")
fun sample(#Header("Authorization") token: String): Call<ResponseBody>
Calling the Service
private fun pushTransactionsToWebApi() {
val vApiService = ApiServiceBuillder.buildService(ApiService::class.java)
CoroutineScope(Main).launch {
var token = SharedDataManager.getInstance(context!!).applicationToken
var tokenArr = token!!.split(':')
responseFromApi = tokenArr[1] ==> I use this so I can remove the word "token" at the beginning of the token string
token = "Bearer ${responseFromApi}"
Log.i("TAG", "${token}") ==> ####
val call = vApiService.sample(token)
if(!call.isExecuted) {
call.enqueue(object : Callback<ResponseBody>{
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
responseFromApi = t.message
}
override fun onResponse(
call: Call<ResponseBody>,
response: Response<ResponseBody>
) {
if(response.isSuccessful){
Toast.makeText(context, "We are OK", Toast.LENGTH_LONG).show()
} else {
progressDialog!!.dismiss()
Toast.makeText(context, "We are NOT OK", Toast.LENGTH_LONG).show()
}
}
})
}
}
}
### => Result in my Log.i()
2020-04-08 13:03:09.235 14185-14185/com.kotlin.ambulantlcs I/TAG:
Bearer
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ODgwNSIsInVzZXJJZCI6IjEiLCJmaXJzdE5hbWUiOiJKdWFuIiwibWlkZGxlTmFtZSI6IkEuIiwibGFzdE5hbWUiOiJEZWxhIENydXoiLCJ0cmFuc2FjdGlvbktleSI6IjJkNjZlYzMxLWI5M2ItNDI2ZC1hMzJlLTM0Yjc4OWE4M2E3OCIsInJldmVudWVEYXRlIjoiMjIvMDMvMjAyMCAyOjI0OjM0IFBNIiwic2hpZnQiOiIyIiwic29zSWQiOiIxMjM0NTYiLCJzb2RJZCI6IjY4IiwicGxhemEiOiI4MDMiLCJoYXNEZXBhcnRlZCI6IkZhbHNlIiwianRpIjoiNjhkMDdmNzEtMThiYy00NmQwLTg3YzEtY2MxMjk4YjgxZDkwIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjpbIlVzZXIiLCJBZG1pbiJdLCJleHAiOjE1ODY0MDg1NzUsImlzcyI6Imh0dHA6Ly8xOTIuMTY4LjEuNDo1MDAwIn0.m1mZw79KLIxq4pZPmBRbN7TjILvhvbUIJOCWDEM8I-k"}
If I paste this in my Postman, I can get access
What do I need to do? Thank you!
From our conversation in the comments, it seems like you're getting a json {"token": "..."} from SharedDataManager.getInstance(context!!).applicationToken. This explains why when you split in : you get printed in the log "..."}.
There are a lot of ways to deserialize json in Android. Here are some options. I think the vanilla way is something like:
val root = JSONObject(SharedDataManager.getInstance(context!!).applicationToken)
val token = root.getString("token")
With this you'll have the token in token.
However, if you already have a json library you could use it. For example, with gson you could do something like:
data class TokenData(
#SerializedName("token")
val token: String)
val token = Gson().fromJson(
SharedDataManager.getInstance(context!!).applicationToken,
TokenData::class.java)
You can now use token.
With Moshi using the kotlin gen library - com.squareup.moshi:moshi-kotlin-codegen - you can define the above model like:
#JsonClass(generateAdapter = true)
data class TokenData(
#Json(name = "token")
val token: String)
// Then get it like:
val token = Moshi.Builder()
.build()
.adapter(TokenData::class.java)
.fromJson(SharedDataManager.getInstance(context!!).applicationToken)
These are just some options. There's also the popular Jackson. Pick the one that suits best your needs. Hope this helps
Remove " " quotes from token
make sure that keys must be same
pass token like as:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ODgwNSIsInVzZXJJZCI6IjEiLCJmaXJzdE5hbWUiOiJKdWFuIiwibWlkZGxlTmFtZSI6IkEuIiwibGFzdE5hbWUiOiJEZWxhIENydXoiLCJ0cmFuc2FjdGlvbktleSI6IjJkNjZlYzMxLWI5M2ItNDI2ZC1hMzJlLTM0Yjc4OWE4M2E3OCIsInJldmVudWVEYXRlIjoiMjIvMDMvMjAyMCAyOjI0OjM0IFBNIiwic2hpZnQiOiIyIiwic29zSWQiOiIxMjM0NTYiLCJzb2RJZCI6IjY4IiwicGxhemEiOiI4MDMiLCJoYXNEZXBhcnRlZCI6IkZhbHNlIiwianRpIjoiNjhkMDdmNzEtMThiYy00NmQwLTg3YzEtY2MxMjk4YjgxZDkwIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjpbIlVzZXIiLCJBZG1pbiJdLCJleHAiOjE1ODY0MDg1NzUsImlzcyI6Imh0dHA6Ly8xOTIuMTY4LjEuNDo1MDAwIn0.m1mZw79KLIxq4pZPmBRbN7TjILvhvbUIJOCWDEM8I-k
I'm trying to post some data with retrofit 2 but I'm gettins some problems... and don't find any example like this...
This is the body that I have to send:
{
"birthday": "12-01-1987",
"name": bob,
"activity": {
"activity_preferences": {
"user_subjects": [4,7,8],
"user_allergies": [1,6,10],
}
}
}
This is my data class:
data class GenericFormDataEntity(
var birthday: String,
var name: String,
#SerializedName("activity")
var food: ActivityEntity?
)
data class ActivityEntity(#SerializedName("activity_preferences")val activityPreferences: ActivityPreferencesEntity)
data class ActivityPreferencesEntity(#SerializedName("user_Subjects")var userSubjects:List<Int>?,#SerializedName("user_allergies")var userAllergies: List<Int>?)
This is the method that I'm trying to build the json:
fun getUserFormEntity(): String{
val paramObject = JSONObject()
paramObject.put("birthday", birthday)
paramObject.put("name", name)
paramObject.put("activity", getActivityEntity())
return paramObject.toString()
}
private fun getActivityEntity(): ActivityEntity{
return ActivityEntity(ActivityPreferencesEntity(selectedSubjectList, selecteAllergiesList))
}
And this is the json that is returning me:
{\"birthday\":\"23-12-2019\",\"name\":Bob,"activity\":\"ActivityEntity(activity_preferences=ActivityPreferencesEntity(user_Subjects=[4,7,8], user_allergies=[1,6,10])"}"
My question is, how can I get the correct json that I have to send as a body:
#Headers("Accept: application/json")
#POST("xxxxxxxx")
suspend fun saveUserData(#Body userFormData: String)
You need to stringify getActivityEntity using Gson.
Gson.toJson(getActivityEntity())
Also, from your API I infer that you are using retrofit why not pass along the entire instance of GenericFormDataEntity as the body for your API.
For enabling this you need to follow by adding GsonConverterFactory.create(gson) to your retrofit.
Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create(gson))
.callFactory(okHttpClient)
.build()