RxJava+Retrofit+Gson JsonSyntaxException on server error response - android

I have REST POST call create order as Observable<OrderResponse>, when order create call is successful everything is fine, but then server returns error I get com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $ because Gson does not know how to handle json that have different fields than my response model
Server error response:
[{
"code": 99,
"message": "Please check your request"
}]
OrderResponse
data class OrderResponse(
#Expose
var orderId: String,
#Expose
var redirectUrl: String,
#Expose
var validUntil: Long
)
RxJava subscription
repository.requestCreateNewOrder(newOrder)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<OrderResponse> {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onNext(t: OrderResponse) {
}
override fun onError(t: Throwable) {
//HERE I GET JsonSyntaxException
}
})
Retrofit Service
interface OrderService {
#Headers(
"Content-Type: application/x-www-form-urlencoded ",
"Connection: Keep-Alive",
"Accept-Encoding: application/json",
"User-Agent: Fiddler"
)
#FormUrlEncoded
#POST("/createOrder")
fun createOrder(#Field("orderId") orderId: String,
#Field("payCurrency") payCurrency: String,
#Field("payAmount") payAmount: Double,
#Header("Content-Length") length: Int): Observable<OrderResponse>}
Anyone have any suggestions for how to pass retrofit or gson the error model to know how to handle it

As you are using GSON to parse the JSON.
Your JSON sucessfull response will be something like
{
"orderId": 1,
"redirectUrl": "url",
"validUntil": 12414194
}
while for error response your JSON response start with Array.
[{
"code": 99,
"message": "Please check your request"
}]
So tell to server guy to not add the error response in Array [].
If you are getting the response as Array then you have to use list.
repository.requestCreateNewOrder(newOrder)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<List<OrderResponse>> {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onNext(t: OrderResponse) {
}
override fun onError(t: Throwable) {
//HERE I GET JsonSyntaxException
}
})
In Observer response you have to add as List if your JSON response start with Array like error.
Solution : Correct it from backend for not adding Error Response in ARRAY.

As per your code you are only handling the success response
but you need to handle your error response too for this you need to ensure that your API should send you error codes different then 200 (success response generally greater then 200 and less then 300 i.e code >= 200 && code < 300 ) because retrofit consider 200-299 as success
You can achieve this simply by changing your observable return type to
Observable<Response< OrderResponse>>
and after receiving response from server simply check
if (orderResponse.isSuccessful()) {
//here you can handle success by using orderResponse.getbody()
} else {
// here you can display error message and if you further want
// to parse error response from server then use below function
errorOrderResponseHandling(orderResponse);
}
you want to further parse response into error model(in this example OrderAPIError is model class for error response) then below is the function
private void errorOrderResponseHandling(Response<OrderResponse> orderResponse) {
OrderAPIError orderAPIError = null;
try {
orderAPIError = new Gson().fromJson(String.valueOf(new
JSONObject(orderResponse.errorBody().string())), OrderAPIError.class);
// further orderAPIError object you can fetch server message and display
it to user as per your requirement
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Above example is in android not in kotlin but you can get idea and use function accordingly in kotlin

Related

How To Post using Retrofit Android Kotlin [duplicate]

This question already has answers here:
Send Post Request with params using Retrofit
(6 answers)
Closed 1 year ago.
I Have JSON like this and i want to post this data using retrofit android
{
"status": "",
"message": "",
"data": {
"request": {
"textData": "123"
}
}
}
and i don't know how to post this data, does anyone want to help me answer it?
You can do it by creating a POJO or data class (in kotlin) of your request which makes doing things like this easier.
MyRequest.kt
data class MyRequest(
var status: String,
var message: String,
var data: MyData
)
data class MyData(
var request: RequestData
)
data class RequestData(
var textData: String
)
MyApiInterface.kt
interface MyApiInterface {
#POST("/my_url_endpoint")
fun myRequest(
#Body request: MyRequest
): Call<Unit>
}
MyActivity.kt
....
val request = MyRequest(
"Ok",
"My Message",
MyData(
request = RequestData("Hello World")
)
)
RetrofitClient.api.myRequest(request).enqueue(object: Callback<Unit> {
override fun onResponse(call: Call<Unit>, response: Response<Unit>) {
// TODO: some task
}
override fun onFailure(call: Call<Unit>, t: Throwable) {
// TODO: some task
}
})
....
after doing this request if you have added logging interceptor you can check that request being made with following body.
{"data":{"request":{"textData":"Hello World"}},"message":"My Message","status":"Ok"}

Parse unknown JSON key with Moshi

I'm trying to parse a JSON I know almost nothing about.
Example of a JSON response
{
"response": {
"content":{
"xxxxxx": "xxxxx",
"xxxxx": "xxxxxx",
...... indeterminate times
}
}
}
I tried to create an adapter, but nothing works :
#FromJson
fun fromJson(json: Map<String, String>): MyResponse {
Log.d("JSON", json.toString())
return MyResponse(...)
}
Is it possible to achieve this with Moshi? If so, what should I do?

how to handle error in okhttp3 without crash

I am using the following method to handle my requests
override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request())
.let { originalResponse ->
Log.i("AMIRA999", "code : " + originalResponse.code())
when (originalResponse.code()) {
200 -> {
Log.i("AMIRA999", "body : " + getErrorResponse(originalResponse))
originalResponse
}
401, 404 -> {
Log.i("AMIRA999", "body : " + getErrorResponse(originalResponse))
originalResponse
/*return originalResponse.mapToBody(
originalResponse.body()?.contentType(),
getErrorResponse(originalResponse)
)*/
}
else -> {
Log.i("AMIRA999", "body : " + originalResponse.body().toString())
throw BadRequestException()
}
}
}
the method work perfect when the code is 200, but it crash if the code is 404 or 401
what I need to keep returning the json comes from server and does not crash to be able to handle it with error message
how can I do that ?
the crash that I got is the following
retrofit2.HttpException: HTTP 401 UNAUTHORIZED
at com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory$BodyCallAdapter$adapt$2.onResponse(CoroutineCallAdapterFactory.kt:104)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:129)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:206)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
You use retrofit2-kotlin-coroutines-adapter and the exception throwing is by design. Any non-2xx HTTP response such as 401 will throw an exception. You can see this for yourself in the library source code
if (response.isSuccessful) {
deferred.complete(response.body()!!)
} else {
deferred.completeExceptionally(HttpException(response))
}
But this is not a problem. You can still access the response and your JSON by doing catch (e: HttpException) and then calling val yourJson = e.response()?.body() as? YourJson.
Note that retrofit2-kotlin-coroutines-adapter is deprecated and that you should migrate to Retrofit 2.6.0 or newer. Then you can prefix your Retrofit interface functions with suspend so you can write nice idiomatic Kotlin code.
Retrofit 2 has a different concept of handling "successful" requests than Retrofit 1. In Retrofit 2, all requests that can be executed (sent to the API) and for which you’re receiving a response are seen as "successful". That means, for these requests the onResponse callback is fired and you need to manually check whether the request is actually successful (status 200-299) or erroneous (status 400-599).
If the request finished successfully, we can use the response object and do whatever we wanted. In case the error actually failed (remember, status 400-599), we want to show the user appropriate information about the issue.
Example
Error Object
Let’s assume your API sends a JSON error body like this:
{
statusCode: 409,
message: "Email address already registered"
}
Note: you can see your JSON error body by printing response.errorBody()
To avoid these bad user experiences, we’re mapping the response body to a Java object, represented by the following class.
class APIError {
private val statusCode:Int = 0
private val message:String
fun status():Int {
return statusCode
}
fun message():String {
return message
}
}
Error Handler
object ErrorUtils {
fun parseError(response:Response<*>):APIError {
val converter = ServiceGenerator.retrofit()
.responseBodyConverter(APIError::class.java, arrayOfNulls<Annotation>(0))
val error:APIError
try
{
error = converter.convert(response.errorBody())
}
catch (e:IOException) {
return APIError()
}
return error
}
}
Error Handler in Action
Now you can handle error in API response using ErrorUtils like the following.
val call = service.me()
call.enqueue(object:Callback<User>() {
fun onResponse(call:Call<User>, response:Response<User>) {
if (response.isSuccessful())
{
// use response data and do some fancy stuff :)
}
else
{
// parse the response body …
val error = ErrorUtils.parseError(response)
// … and use it to show error information
// … or just log the issue like we’re doing :)
Log.d("error message", error.message())
}
}
fun onFailure(call:Call<User>, t:Throwable) {
// there is more than just a failing request (like: no internet connection)
}
})
The complete example with a video is here retrofit-2-error-handling.

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)
}
})

How to get response status in Retrofit (Kotlin)?

I am using Retrofit. Using Kotlin. I need to know the resonse status code. Like is it 200 or 500. How can I get it from the response ?
My Api class:
interface Api {
#POST("user/code/check")
fun checkSmsCode(#Body body: CheckCodeBody): Single<Response<Void>> }
This is how I am calling Api. But note that SERVE DOES NOT RETURN CODE FIELD IN RESPONSE BODY!
api.checkSmsCode(
CheckCodeBody(
code = code
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
//HOW TO CHECK STATUS RESPONSE STATUS CODE HERE???
},
{ e ->
when (e) {
is IOException -> view?.showNoNetworkAlert()
else -> view?.invalidCodeError()
}
}
).also {}
As I understood, in Java it was a easy peasy thing.
You just use response.code() or something similar and that's it. But how to achieve it in Kotlin?
so your on response should look something like this
override fun onResponse(call: Call<MyModel>?, response: Response<MyModel>?) {
//
}
})
then inside that you should just to able to do
override fun onResponse(call: Call<MyModel>?, response: Response<MyModel>?) {
response.code()
}
})
is this what your talking about?
yo need to use it
interface OnlineStoreService{
#Headers("Content-Type: application/json","Connection: close")
#POST
fun getDevices(
#Url url: String,
#Header("Authorization") token: String,
#Body apiParams: APIParams
): Observable<OnlineStoresInfo>
}
.subscribe({ onlineStoresInfo -> // or it -> where "it" it's your object response, in this case is my class OnlineStoresInfo
loading.value = false
devices.value = onlineStoresInfo.devices
}, { throwable ->
Log.e(this.javaClass.simpleName, "Error getDevices ", throwable)
loading.value = false
error.value = context.getString(R.string.error_information_default_html)
})
.subscribe({ it ->
// code
}, { throwable ->
//code
})
If you haven't configure your retrofit request method to return a Response<*> you won't be able to have the response code. Example:
interface SomeApi{
#POST("user/code/check")
fun checkSmsCode(#Body body: CheckCodeBody): Single<Response<String>>
}
And after you finish your request:
.subscribe({
//access response code here like : it.code()
//and you can access the response.body() for your data
//also you can ask if that response.isSuccessful
})

Categories

Resources