How to use the OpenAPI generators retrofit stub with android kotlin client? Response 501 "Not Implemented" - android

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.

Related

Retrofit adding extra slashes when I upload data

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)

Unable to create call adapter for X model for X method

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

Always getting 401 when using android device but not in Postman

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

How to read the response data of Apollo Client response/ GraphQL response in Kotlin Andorid

I am developing an Android application using Kotlin. In my application, I am consuming GraphQL API using Apollo Client. What I am trying to do now is that I want to retrieve a response field of the response.
This is my code
protected fun _handleLoginButtonClick(view: View) {
val apolloClient = ApolloClient.builder()
.serverUrl("https://app.herokuapp.com/graphql")
.okHttpClient(OkHttpClient())
.build()
val loginMutation = LoginMutation.builder()
.identity(view.etf_email.text.toString())
.password(view.etf_password.text.toString())
.build()
view.tv_login_error_message.text = "Started making request"
apolloClient.mutate(loginMutation).enqueue(object: ApolloCall.Callback<LoginMutation.Data>() {
override fun onFailure(e: ApolloException) {
view.tv_login_error_message.text = e.message
}
override fun onResponse(response: Response<LoginMutation.Data>) {
//here I dont know how to retrieve a field, accessToken
}
})
}
As you can see the comment in the onResponse callback, I cannot figure out how to retrieve the accessToken field. How can I retrieve it?
OnResponse Contains response Object and it has data object from where you can get your fields.
apolloClient.mutate(loginMutation).enqueue(object: ApolloCall.Callback<LoginMutation.Data>() {
override fun onFailure(e: ApolloException) {
view.tv_login_error_message.text = e.message
}
override fun onResponse(response: Response<LoginMutation.Data>) {
//here you can use response to get your model data like accessToken
response.data.(here you can get data from your model. eg accessToken)
}
})

Singleton that will hold an instance of Retrofit

So I have created a login that will take an username and password input from the user, encode it with Base64 in order to create a token in the format: ("Authorization", AUTH) where AUTH = "Basic " + Base64 encoding of user and password. This is sent via Headers.
So, in the end, it looks like this: Authorization: Basic XXXXXX, where XXXXXX is the user token.
And then it will check whether or not that user exists in the database via an API request.
I am using Retrofit and OkHttp3 in the same class as RetrofitClient and this class is responsible for using the API and adding those Headers.
Later, I use the RetrofitClient class on the Login Activity.
What I need to do now, is make this "token" available to all the other activities by creating a Singleton that will store the data of the Retrofit after a successful login. But I do not know how to do this.
I started learning Kotlin and Android 3 weeks ago.
Here is my code:
GET_LOGIN.kt
interface GET_LOGIN {
#GET("login")
fun getAccessToken() : Call<String>
}
RetrofitClient.kt
class RetrofitClient {
fun login(username:String, password:String){
val credentials = username + ":" + password
val AUTH = "Basic " + Base64.encodeToString(credentials.toByteArray(Charsets.UTF_8), Base64.DEFAULT).trim()
retrofit = init(AUTH)
}
// Initializing Retrofit
fun init(AUTH: String) : Retrofit{
// Creating the instance of an Interceptor
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
// Creating the OkHttp Builder
val client = OkHttpClient().newBuilder()
// Creating the custom Interceptor with Headers
val interceptor = Interceptor { chain ->
val request = chain?.request()?.newBuilder()?.addHeader("Authorization", AUTH)?.build()
chain?.proceed(request)
}
client.addInterceptor(interceptor) // Attaching the Interceptor
//client.addInterceptor(logging) // Attaching the Interceptor
// Creating the instance of a Builder
val retrofit = Retrofit.Builder()
.baseUrl("https://srodki.herokuapp.com/") // The API server
.client(client.build()) // Adding Http Client
.addConverterFactory(GsonConverterFactory.create()) // Object Converter
.build()
return retrofit
}
lateinit var retrofit : Retrofit
fun providesGetLogin(): GET_LOGIN = retrofit.create(GET_LOGIN::class.java)
}
LoginActivity.kt
var RetrofitClient : RetrofitClient = RetrofitClient()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
loginBtn.setOnClickListener {
val user = userTxt.text.toString()
val pass = passTxt.text.toString()
if (validateLogin(user, pass)){
login(user, pass)
}
}
}
fun validateLogin(user: String, pass: String): Boolean {
if (user == null || user.trim().isEmpty()){
Toast.makeText(this, "Missing Username or Password", Toast.LENGTH_SHORT).show()
return false
}
if (pass == null || pass.trim().isEmpty()){
Toast.makeText(this, "Missing Username or Password", Toast.LENGTH_SHORT).show()
return false
}
return true
}
fun login(user: String, pass: String) {
RetrofitClient.login(user, pass)
val apiLogin = RetrofitClient.providesGetLogin().getAccessToken()
apiLogin.enqueue(object : Callback<LoginResponse> {
override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
if(response.isSuccessful){
if(response.body()?.code == 0){
Toast.makeText(this#LoginActivity, "Login Successful!", Toast.LENGTH_SHORT).show()
val intent = Intent(this#LoginActivity, List_usersActivity::class.java)
startActivity(intent)
} else {
Toast.makeText(this#LoginActivity, "Login Failed.", Toast.LENGTH_SHORT).show()
}
}
}
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
Toast.makeText(this#LoginActivity, "Login Failed.", Toast.LENGTH_SHORT).show()
}
})
}
}
first and foremost, please use camel case on java and kotlin. We have standards in java and kotlin on programming. And i can see that you are trying to do DI, but, thats not how you do it in Android.
Anyways, you could do this a couple of ways without even using a singleton but by saving it on a storage. Options are Shared Preferences, Local Storage and SQLite. But, if you insist on using a singleton. You can do it like this:
object MySingleton { // This is how you declare singletons in kotlin
lateinit var token: String;
}
EDIT
So, from your comment, it looked like you need to store the token. You could start by using sharedpreferences(database would be better) and store the token there. I assume you don't know how to so here is an example:
val sp = SharedPreferences("sp", 0);
sp.edit().putString("token", theTokenVariable); // not sure of this function
sp.edit().apply(); // you could use commit if you dont mind sharedpreferences to lag your screen(if it ever will)
Now how do you get the token from retrofit? The only way i could help you right now is that you could retrieve the response body from the response variable you receive from onResponse of the retrofit call. From there it is your problem mate. I don't know how your response is formatted, how it should be retrieved etc. A recommendation would be to format it as JSON.

Categories

Resources