At the moment I try to learn more about Retrofit and Kotlin. For that reason, I went to the website
GraphQL and REST API for Testing and Prototyping to practice getting JSON and all possible responses including error messages. However, at the moment I try to solve one interesting issue.
On their website, they mentioned that the "Response format Object or Array of Objects {} or [{}]"
The issue is that my POST call expects Object which is ok if everything goes ok, however, if something goes wrong I do not have access to the error message becase it is in different format [{}] that the Retrofit expect {}. I will appriciate any help.
Data class User:
class User(
#SerializedName("id")
val id: String?,
#SerializedName("name")
val name: String?,
#SerializedName("email")
val email: String?,
#SerializedName("status")
val status: String?,
#SerializedName("gender")
val gender: String?
)
Retrofit:
#POST("users")
#Headers("Accept:application/json", "Content-Type:application/json",
"Authorization: Bearer ...............")
fun createUser(#Body params: User): Call<User>
ViewModel:
fun createUser(user: User) {
val retroInstance =
RetrofitInstance.getRetrofitInstance().create(RetrofitService::class.java)
val call = retroInstance.createUser(user)
call.enqueue(object : Callback<User?> {
override fun onFailure(call: Call<User?>, t: Throwable) {
createNewUserLiveData.postValue(null)
}
override fun onResponse(call: Call<User?>, response: Response<User?>) {
if (response.isSuccessful) {
createNewUserLiveData.postValue(response.body())
} else {
createNewUserLiveData.postValue(null)
}
}
})
}
Response when everything is ok:
{
"id": 295246,
"name": "Test3",
"email": "test3#google.com",
"gender": "male",
"status": "active"
}
Response from the same call when something goes wrong that I can't catch:
[
{
"field": "email",
"message": "has already been taken"
}
]
Related
i am new to kotlin and i am in learning phase. I have followed many links but didn't able to understand completely.
I want Json response to show in my textview.
Problem: 1
I have tried this code but was unable to get data, but i want to get the items inside data object. Quote and author are coming null.
{
"status": 200,
"message": "Success",
"data": {
"Quote": "The pain you feel today will be the strength you feel tomorrow.",
"Author": ""
},
"time": "0.14 s"
}
Problem: 2
I dont know how to parse this response in textview
object ServiceBuilder {
private val client = OkHttpClient.Builder().build()
private val retrofit = Retrofit.Builder()
.baseUrl("https://url.com.pk/") // change this IP for testing by your actual machine IP
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
fun<T> buildService(service: Class<T>): T{
return retrofit.create(service)
}}
RestApi
interface RestApi{
#Headers("Content-Type: application/json")
#POST("api/getquotes")
abstract fun addUser(#Body userData: UserInfo): Call<UserInfo>}
RestAPiService
class RestApiService
{
fun addUser(userData: UserInfo, onResult: (UserInfo?) -> Unit)
{
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<UserInfo>
{
override fun onFailure(call: Call<UserInfo>, t: Throwable)
{
onResult(null)
}
override fun onResponse( call: Call<UserInfo>, response: Response<UserInfo>)
{
val addedUser = response.body()
Log.d("responsee",""+addedUser)
onResult(addedUser)
}
}
)
}
}
UserInfo
data class UserInfo (
#SerializedName("Quote")
val quote : String,
#SerializedName("Author")
val author : String
)
MainActivity
fun getQuotes() {
val apiService = RestApiService()
val userInfo = UserInfo("","")
apiService.addUser(userInfo) {
Log.d("Error registering user","errter")
/*if ( != null)
{
// it = newly added user parsed as response
// it?.id = newly added user ID
} else {
Log.d("Error registering user","errter")
}*/
}
}
Any help would be appreciated :)
Status, message and data are all part of the response so you need to take care of that. For example this
data class AddUserResponse(
val `data`: UserInfo, //like you defined it
val message: String,
val status: Int,
val time: String
)
This means parameter and response are different so the RestApi needs to be changed to this
abstract fun addUser(#Body userData: UserInfo): Call<AddUserResponse>}
This in turn also change the types in the service like
class RestApiService
{
fun addUser(userData: UserInfo, onResult: (UserInfo?) -> Unit)
{
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<AddUserResponse>
{
override fun onFailure(call: Call<AddUserResponse>, t: Throwable)
{
onResult(null)
}
override fun onResponse( call: Call<AddUserResponse>, response: Response<AddUserResponse>)
{
val addedUser = response.body()
Log.d("responsee",""+addedUser)
onResult(addedUser.data)
}
}
)
}
}
now in getQuotes you will have that it is a UserInfo object
apiService.addUser(userInfo) {
val returnedUserInfo = it
}
just follow my steps :
File->settings->Plugins
search for JSON To Kotlin class and install it
again click on File->New->Kotlin Data class from JSON
paste your json code here and click on generate. It will generate POJO classes and you will good to go.
The first thing I noticed, is that the data in your json is:
"Quote": "The pain you feel today will be the strength you feel tomorrow.",
"Author": ""
While your UserInfo defined #SerializedName("message") for Quote.
I am trying to make a simple GET API call from Android app via Retrofit and Kotlin.
What I am struggling with is getting the data from a certain tag of response.
I am using Django rest framework with pagination, therefore results are enclosed in tag results. I can't figure out how to look into this results tag i.o. the whole response from retrofit.
My response:
{
"count": 13,
"next": null,
"previous": null,
"results": [
{
"id": 2,
"passport_number": 11233546,
"first_name": "Egor",
"last_name": "Wexler",
"email": "string",
"age": 0,
"city": "city"
},
...
{ <other customers> },
]
}
Customer.kt
import com.google.gson.annotations.SerializedName
data class Customer(
#SerializedName("passport_number") val passportNumber: Int,
#SerializedName("first_name") val firstName: String,
#SerializedName("last_name") val lastName: String,
#SerializedName("email") val email: String,
#SerializedName("age") val age: Int,
#SerializedName("city") val city: String
)
ApiInterface.kt
interface ApiInterface {
#GET("customers/")
fun getCustomers() : Call<List<Customer>>
companion object {
fun create() : ApiInterface {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
return retrofit.create(ApiInterface::class.java)
}
}
}
MainActivity.kt
val apiInterface = ApiInterface.create()
apiInterface.getCustomers().enqueue(
object : Callback<List<Customer>> {
override fun onResponse(call: Call<List<Customer>>, response: Response<List<Customer>>) {
// logic for success
}
override fun onFailure(call: Call<List<Customer>>, t: Throwable) {
t.message // java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
}
}
)
So the code eventually went to onFailure with the message that I put in the comment.
If I disable pagination on server - it works as expected because the response turns to what is according to app expectations (list of objects Customer).
But I want the app to look into tag results since it actually contains the response.
I feel like this should be trivial to do but can't find how.
I saw a lot of answers for the similar question marked by "Expected BEGIN_ARRAY but was BEGIN_OBJECT" but none of them solves my issue.
You should create a response class that follows the same structure as your response.
In your case something like this will do:
data class PageResult(
val count: Int,
val next: Int?,
val previous: Int?,
val results: List<Customer>
)
And in your API interface use:
#GET("customers/")
fun getCustomers() : Call<PageResult>
I am trying to get response body from this url:http:"//192.168.0.220:8000/records/?account_id=2"
In android studio i get status 200 but body is always null. Can anybody please tell me what I am doing wrong?
Response in postman looks like this:
{
"result": [
{
"id": 1,
"account_id": 2,
"title": "ez",
"datetime": "2021-03-21T00:00:00",
"description": "ez2",
"image": null,
"recording": null
},
{
"id": 2,
"account_id": 2,
"title": "ez",
"datetime": "2021-03-21T00:00:00",
"description": "ez2",
"image": null,
"recording": null
},
....
Response in android studio:
I/System.out: Response{protocol=http/1.1, code=200, message=OK, url=http://192.168.0.220:8000/records/?account_id=2}
Item(id=null, account_id=null, title=null, datetime=null, image=null, recording=null)
Interface:
interface Gett {
#GET("?account_id=2")
fun getRecord(): Call<Record.Item>
}
Class:
class Record {
data class Item(
val id: String,
val account_id: String,
val title: String,
val datetime: String,
val image: String,
val recording: String
)
}
MainActivity:
val retrofit = Retrofit.Builder()
.baseUrl("http://192.168.0.220:8000/records/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(Gett::class.java)
val call = service.getRecord()
call.enqueue(object : retrofit2.Callback<Record.Item> {
override fun onResponse(call: Call<Record.Item>, response: Response<Record.Item>) {
if (response.code() == 200) {
println(response)
println(response.body()!!)
}
}
override fun onFailure(call: Call<Record.Item>, t: Throwable) {
println("fail")
}
})
The issue might be this
interface Gett {
#GET("?account_id=2")
fun getRecord(): Call<Record.Item> <----
}
So, change your model
data class Record (
val result: List<Item>
)
data class Item(
val id: String,
val account_id: String,
val title: String,
val datetime: String,
val image: String,
val recording: String
)
As I can see your JSON has an array of your Item so change it to.
interface Gett {
#GET("?account_id=2")
fun getRecord(): Call<Record>
}
Thanks, #Kunn for pointing out JSONObject.
I want to show response POST from retrofit. I don't really know how because my response are array but with POST.
this is how my response looks like
{
"message":[
"00",
"Get Success"
],
"result":{
"listProgram":[
{
"banner":"",
"area":"",
"domainlembaga":"",
"domainprogram":"",
"donate":0
{
.
.
.
]
service
interface Service {
#POST("program/list")
fun getProgram(#Body body: Pair<String, Int>, pair: Pair<String, Int>): Call<BaseBersedekahResponse<ListProgramBersedekahResponse>>
data
data class BaseBersedekahResponse<T>(
#SerializedName("message") val message: String?,
#SerializedName("result") val result: T?
)
data class ListProgramBersedekahResponse(
#SerializedName("listProgram") val listProgram: List<ProgramBersedekahResponse>?
)
data class ProgramBersedekahResponse(
#SerializedName("banner") val banner: String?,
#SerializedName("domainlembaga") val domainlembaga: String?,
#SerializedName("domainprogram") val domainprogram: String?,
#SerializedName("donate") val donatur: Int?
)
activity
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(APIUrl.BASEURL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService: Service = retrofit.create(Service::class.java)
val call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>> = apiService.getProgram(
"limit" to 1,
"group" to 4)
call.enqueue(object : Callback<BaseBersedekahResponse<ListProgramBersedekahResponse>> {
override fun onResponse(call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>, response: Response<BaseBersedekahResponse<ListProgramBersedekahResponse>>) {
val getProgram: List<ProgramBersedekahResponse?>? = response.body().result.listProgram
Toast.makeText(applicationContext, "success", Toast.LENGTH_SHORT).show()
}
override fun onFailure(call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>>, t: Throwable) {
Toast.makeText(applicationContext, "Failure", Toast.LENGTH_SHORT).show()
}
})
I actually planning it to show in RecyclerView, but I still don't know how to show it the response from POST. please help.
Your signatures should match each other. You should make your signatures same as the signature in your interface.
For your request body:
data class BersedekahRequest(
#SerializedName("limint") val limint: Int,
#SerializedName("group") val group: Int
)
then your interface:
#POST("program/list")
fun getProgram(#Body body: BersedekahRequest): Call<BaseBersedekahResponse<ListProgramBersedekahResponse>>
your rest service call:
val requestBody = BersedekahRequest(10,20)
val call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>> = apiService.getProgram(requestBody)
call.enqueue(object : Callback<BaseBersedekahResponse<ListProgramBersedekahResponse>> {
override fun onResponse(call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>, response: Response<BaseBersedekahResponse<ListProgramBersedekahResponse>>) {
val GetApps2: List<ProgramBersedekahResponse?>? = response.body().result.listProgram
Toast.makeText(applicationContext, "success", Toast.LENGTH_SHORT).show()
}
override fun onFailure(call: Call<ListProgramBersedekahResponse>, t: Throwable) {
Toast.makeText(applicationContext, "Failure", Toast.LENGTH_SHORT).show()
}
})
And I think one more problem you have.
fun getProgram(#Body body: ProgramBersedekahResponse?) this #Body is the body you will send via POST request, not your response body. If you do not send anything inside post body, make it empty, or give the appropriate request body since it seems like it is your response object right now.
I have a app whos call a service POST (postman test) and get some information from that call.
My retrofit initializer:
class RetrofitInitializer {
private val retrofit = Retrofit.Builder()
.baseUrl("https://bank-app-test.herokuapp.com/api/")
.addConverterFactory(GsonConverterFactory.create())
.build()
fun loginApiContract() : LoginApiContract{
return retrofit.create(LoginApiContract::class.java)
}
}
the interface:
interface LoginApiContract {
#POST("login")
fun login() : Call<UserAccount>
#GET("statements")
fun getStatements()
}
and finally the call:
val call = RetrofitInitializer().loginApiContract().login()
call.enqueue(object: Callback<UserAccount> {
override fun onResponse(call: Call<UserAccount?>?,
response: Response<UserAccount?>?) {
response?.body()?.let {
val myUserAccount: UserAccount = it
loginView.doLogin(myUserAccount)
}
}
override fun onFailure(call: Call<UserAccount?>?,
t: Throwable?) {
Log.e("onFailure error", t?.message)
}
})
I got response code 200, but response body is empty.
This is my postman response:
{
"userAccount": {
"userId": 1,
"name": "Jose da Silva Teste",
"bankAccount": "2050",
"agency": "012314564",
"balance": 3.3445
},
"error": {}
}
and this is my model:
class UserAccount constructor(var userId: Int, var name: String, var bankAccount: String, var agency: String, var balance: Double){
init{
this.userId = userId
this.name = name
this.bankAccount = bankAccount
this.agency = agency
this.balance = balance
}
}
I found!
Its a problem with my postman. For any reason my AVD cant access the mock server. now i solve my problem with a simple restful api node.js.
Thx for the help guys.
Your response contains elements named "userAccount" and "error". Your UserAccount class has neither which is causing the issue. Therefore, use Retrofit with a class like this:
data class UserResponse(val userAccount: UserAccount, val error: BackendError)