onSuccess() of retrofit
override fun onSuccess(call: Call<Any?>?, response: Response<Any?>, tag: Any?) {
when (tag) {
RequestCodes.API.LOGIN -> {
val apiResponse = response.body() as? ModelAPIResponse
val toast = Toast.makeText(
MainApplication.applicationContext(),
"Api Success=${apiResponse?.message}",
Toast.LENGTH_SHORT
)
toast.show()}}}
Image of database body
Model For Api Response
#SerializedName("statusCode")
var statusCode: String? = null
#SerializedName("message")
var message: String? = null
try make the Json Response to Object class Like this
data class ApiResponse(
#SerializedName("statusCode")
var statusCode: String? = null
#SerializedName("message")
var message: String? = null
)
then on your retrofit API call
#GET("your_endpoint")
fun getCartListAsync(): Call<ApiResponse>
then on your success change the Call<Any> into Call<ApiResponse>
I have Found a Way to map the response the do the same thing in IOS which is writing a mapper
val apiResponse = ModelAPIResponse(response.body() as LinkedTreeMap<String, String>)
then in data model
class ModelAPIResponse(data: LinkedTreeMap<String, String>) {
var statusCode: String? = null
var message: String? = null
init {
this.message = data["message"]
this.statusCode = data["statusCode"]
}}
Related
With retrofit I get response LevelsEntity but if I get error it get me ResponseError, NOTE: I cant merge LevelsEntity and ResponseError together in one entity.
LevelsEntity:
class LevelsEntity : ArrayList<LevelsEntityItem>()
LevelsEntityItem:
data class LevelsEntityItem(
#SerializedName("category")
val category: Int? = null,
#SerializedName("completed")
val completed: Boolean? = null,
#SerializedName("completionhascriteria")
val completionhascriteria: Boolean? = null
)
ResponseError:
data class ResponseError(
#SerializedName("errorcode")
val errorcode: String? = null,
#SerializedName("exception")
val exception: String? = null,
#SerializedName("message")
val message: String? = null
)
And I create bellow class for get multiple data like bellow:
class BaseLevelsEntity<LevelsEntity, ResponseError> {
var levelsEntity: LevelsEntity? = null
var responseError: ResponseError? = null
val isSuccess: Boolean
get() = responseError == null
}
And in my #POST of retrofit is:
#POST("/webservice/rest/server.php")
suspend fun getPopularLevelsInLessonsF(
#Query("mdwsrestformat") mdwsrestformat: String?,
#Field("wsfunction") wsfunction: String?,
#Field("wstoken") wstoken: String?,
#Field("userid") userid: Int?
): Call<BaseLevelsEntity<LevelsEntity, ResponseError>>
But I cant get any result in my impl:
class LessonsRepositoryImpl(
private val lessonsRemoteDatasource: LessonsRemoteDatasource
) : LessonsRepository {
override suspend fun getLevelsInLessonsF(
wstoken: String,
userid: Int
): Resource<BaseLevelsEntity<LevelsEntity, ResponseError>> {
return responseToResource(lessonsRemoteDatasource.getLevelsValueInLessonsF(wstoken, userid).execute())
}
private fun responseToResource(response: Response<BaseLevelsEntity<LevelsEntity, ResponseError>>): Resource<BaseLevelsEntity<LevelsEntity, ResponseError>> {
if (response.isSuccessful) {
if (response.body() != null) {
response.body()?.let { result ->
if (!result.levelsEntity.isNullOrEmpty()) {
if (result.levelsEntity!!.size > 0) {
return Resource.Success(result)
}
} else if (result.responseError != null) {
return Resource.Error(result.responseError?.errorcode ?: "unknown")
}
}
} else {
return Resource.Error("unknown_info")
}
}
return Resource.Error(response.message())
}
}
Normally response should be in common format.
If cannot do this from backend then you can receive response as JsonObject and then check the key manually in repository to decide if it is success or error response. Based on that you can then convert the response to object with gson.
I am using retrofit2.6.2 for api call. LoggingInterceptor showing full response in logcat but retrofit response body return null. I didn't figure out where is my problem.
My json data schema is
{
"error":false,
"msg":"Banner Found",
"id":"9",
"activity":"VipPremium1",
"imageUrl":"https:\/\/1.bp.blogspot.com\/-Kh3RQlJH7Xw\/X-1mIPi7_HI\/AAAAAAAAFME\/Y2bCnU5odngcdDT83uC9QwUr7IGJdTDfACLcBGAsYHQ\/s2616\/COMPRESSED_IMG_1609393684674.jpg",
"actionUrl":"https:\/\/www.youtube.com\/watch?v=ukJX5ZgJec4",
"actionType":1,
"visible":true
}
Model Class BannerRes
data class BannerRes(
#SerializedName("actionType")
val actionType: Int?,
#SerializedName("actionUrl")
val actionUrl: String?,
#SerializedName("activity")
val activity: String?,
#SerializedName("error")
val error: Boolean?,
#SerializedName("id")
val id: String?,
#SerializedName("imageUrl")
val imageUrl: String?,
#SerializedName("msg")
val msg: String?,
#SerializedName("visible")
val visible: Boolean?
)
Api Interface
#GET("api/helper.getBanner.php")
suspend fun getBanner(
#Query("bannerName") bannerName: String,
): Response<BannerRes>
Api call done here
private fun loadPremiumBanner() {
Coroutines.main {
val res = viewModel.getBanner("VipPremium1")
Log.d("Response", res.body()!!.msg!!)
}
}
When I print response body using
Log.d("Response", Gson().toJson(res.body()))
It shows the the response in logcat,
Logcat
{"error":false,"msg":"Banner Found","id":"9","activity":"VipPremium1","imageUrl":"https://1.bp.blogspot.com/-Kh3RQlJH7Xw/X-1mIPi7_HI/AAAAAAAAFME/Y2bCnU5odngcdDT83uC9QwUr7IGJdTDfACLcBGAsYHQ/s2616/COMPRESSED_IMG_1609393684674.jpg","actionUrl":"https://www.youtube.com/watch?v\u003dukJX5ZgJec4","actionType":1.0,"visible":true}
but when access res.body()!!.msg It shows null.
Retrofit Setup
companion object {
#Volatile
private var myApiInstance: MyApi? = null
private val LOCK = Any()
operator fun invoke() = myApiInstance ?: synchronized(LOCK) {
myApiInstance ?: createClient().also {
myApiInstance = it
}
}
private fun createClient(): MyApi {
val AUTH: String = "Basic ${
Base64.encodeToString(
("${BuildConfig.USER_NAME}:${BuildConfig.USER_PASSWORD}").toByteArray(),
Base64.NO_WRAP
)
}"
val interceptor = run {
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.apply {
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
}
}
val okHttpClient: OkHttpClient = OkHttpClient.Builder()
.callTimeout(10,TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addInterceptor { chain ->
val original: Request = chain.request()
val requestBuilder: Request.Builder = original.newBuilder()
.addHeader("Authorization", AUTH)
.method(original.method, original.body)
val request: Request = requestBuilder.build()
chain.proceed(request)
}
.build()
val gsonBuilder = GsonBuilder()
gsonBuilder.setLenient()
val gson = gsonBuilder.create()
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addConverterFactory(ScalarsConverterFactory.create())
.client(okHttpClient)
.build()
.create(MyApi::class.java)
}
}
I resolved this issue by adding kotlin model data class member filed default value. I don't know what is the reason behind this,
Old data class
data class BannerRes(
#SerializedName("actionType")
val actionType: Int?,
#SerializedName("actionUrl")
val actionUrl: String?,
#SerializedName("activity")
val activity: String?,
#SerializedName("error")
val error: Boolean?,
#SerializedName("id")
val id: String?,
#SerializedName("imageUrl")
val imageUrl: String?,
#SerializedName("msg")
val msg: String?,
#SerializedName("visible")
val visible: Boolean?
)
Modified or data class with member field default value which fix my problem
data class BannerRes(
#SerializedName("error") var error : Boolean = true,
#SerializedName("msg") var msg : String? = null,
#SerializedName("id") var id : String? = null,
#SerializedName("activity") var activity : String? = null,
#SerializedName("imageUrl") var imageUrl : String? = null,
#SerializedName("actionUrl") var actionUrl : String? = null,
#SerializedName("actionType") var actionType : Int = 0,
#SerializedName("visible") var visible : Boolean = false
)
I think you can't use both Gson and Scalars Converter in Retrofit because retrofit confuse to wrap it.
Remove Scaler (I prefer Gson) and try again.
If not work then use GsonConverterFactory.create() this.
With Retrofit you can consume the response body only once. So the first call to body() will return the response but further calls will not. You're consuming your body when you're logging it.
I haven't written anything here for a long time, but now I really need advice )
I'm using Retrofit2 as api client. Server API has one endpoint, for example /api/stats which receive JSON body request and return JSON response as:
data class StatsResult<T>(
#SerializedName("code") val code: Int,
#SerializedName("message") val msg: String?,
#SerializedName("request_id") val requestId: String?,
#SerializedName("data") val data: T?
)
If some error, data is null.
Otherwise data is an array that can contain different type of data depending on the type of request.
For example:
Request1:
{
"type": "type1",
"params": {
}
}
Response:
{
"code": 0,
"request_id": "...",
"data": [
{
"key1": "value1",
"key2": "value2"
},
{
"key1": "value3",
"key2": "value4"
}
]
}
Request2:
{
"type": "type2",
"params": {
}
}
Response:
{
"code": 0,
"request_id": "...",
"data": [
{
"key3": "value1",
"key4": "value2"
},
{
"key3": "value3",
"key4": "value4"
}
]
}
Here is my implementation in short:
interface StatsApi {
#POST("/api/stats")
suspend fun getStats(#Body request: StatsRequest): ApiResponse<StatsData>
}
sealed class ApiResponse<out T: Any> {
data class Success<T: Any>(val body: T): ApiResponse<T>()
object Unauthorized : ApiResponse<Nothing>()
object Forbidden: ApiResponse<Nothing>()
object NetworkError: ApiResponse<Nothing>()
data class Error(val msg: String? = null): ApiResponse<Nothing>()
data class Exception(val t: Throwable): ApiResponse<Nothing>()
}
typealias StatsData = StatsResult<List<BaseStatsDto>>
open class BaseStatsDto()
class Type1StatsDto: BaseStatsDto() {
#SerializedName("key1") var key1: String? = null
#SerializedName("key2") var key2: String? = null
}
class Type2StatsDto: BaseStatsDto() {
#SerializedName("key3") var key3: String? = null
#SerializedName("key4") var key4: String? = null
}
So I tried to workaround this with open/abstract class BaseStatsDto and than cast it to final class. But this solution didn't work.
For response handling I'm using CallAdapter.Factory() with custom Call<>:
open class ApiResponseCall<T : Any>(
private val delegate: Call<T>
) : Call<ApiResponse<T>> {
override fun enqueue(callback: Callback<ApiResponse<T>>) {
return delegate.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
val rsp = when (t) {
is IOException -> ApiResponse.NetworkError
else -> ApiResponse.Exception(t)
}
callback.onResponse(this#ApiResponseCall, Response.success(rsp))
}
override fun onResponse(call: Call<T>, response: Response<T>) {
val rsp: ApiResponse<T>
rsp = if (response.isSuccessful) {
val body = response.body()
ApiResponse.Success(body as T)
} else {
val code = response.code()
val error = response.errorBody()
when (code) {
401 -> ApiResponse.Unauthorized
403 -> ApiResponse.Forbidden
in 400..499 -> ApiResponse.Error("Client error")
in 500..599 -> ApiResponse.Error("Server error")
else -> ApiResponse.Exception(Exception("Unknown error"))
}
}
callback.onResponse(this#ApiResponseCall, Response.success(rsp))
}
})
}
...
}
I see another solution - to have separate interface functions with separate response types. And it working fine:
#POST("/api/stats")
suspend fun getType1Stats(#Body request: StatsRequest): ApiResponse<StatsResult<List<Type1StatsDto>>>
#POST("/api/stats")
suspend fun getType2Stats(#Body request: StatsRequest): ApiResponse<StatsResult<List<Type2StatsDto>>>
But if statistic data types count increases it will be very uncomfortable to maintain.
I would like to have one statistic api endpoint.
Use http://www.jsonschema2pojo.org/ Set SourceType: JSON and Annonation style: Gson
Data for request 1
class Data1 {
#SerializedName("key1")
#Expose
var key1: String? = null
#SerializedName("key2")
#Expose
var key2: String? = null
}
Response of request1
class Response1 {
#SerializedName("code")
#Expose
var code: Int? = null
#SerializedName("request_id")
#Expose
var requestId: String? = null
#SerializedName("data")
#Expose
var data: List<Data1>? = null
}
Data for request 2
class Data2 {
#SerializedName("key3")
#Expose
var key3: String? = null
#SerializedName("key4")
#Expose
var key4: String? = null
}
Response of request 2
class Response1 {
#SerializedName("code")
#Expose
var code: Int? = null
#SerializedName("request_id")
#Expose
var requestId: String? = null
#SerializedName("data")
#Expose
var data: List<Data2>? = null
}
with
#POST("/api/stats")
suspend fun getStats1(#Body request: StatsRequest1): ApiResponse<Response1>
#POST("/api/stats")
suspend fun getStats2(#Body request: StatsRequest2): ApiResponse<Response2>
So I have to handle a POST request with its body data taken from some input in a form.
The Endpoint of this service is https://someUrl.com/switching-product/switch?orderID=A002&procode=0200011&nip=P19120
The Response returned from Postman is like this.
The body of this request is like this :
In this case, I have this Interface for handling a POST request:
///Endpoint: https://someUrl.com/switching-product/switch?orderID=A002&procode=0200011&nip=P19120
interface editProductDetail{
#FormUrlEncoded
#POST("switch")
fun editProductDetail(#Query("orderID") orderID: String,
#Query("procode") procode: String,
#Query("nip") nip : String,
#Field("procode_new") procodeNew: String,
#Field("item_qty_new") itemQtyNew: String,
#Field("item_price_new") itemPriceNew: String,
#Field("item_name_new") itemNameNew: String,
#Field("total_price_new") totalPriceNew: String): Call<OutstandingOrderDetailPOJODataClassDetailItem>
}
This is the data class I use:
data class OutstandingOrderDetailPOJODataClassDetailItem(
#field:SerializedName("item_price_new")
val itemPriceNew: Int? = null,
#field:SerializedName("item_name_new")
val itemNameNew: String? = null,
#field:SerializedName("total_price")
val totalPrice: Int? = null,
#field:SerializedName("item_price")
val itemPrice: Int? = null,
#field:SerializedName("item_name")
val itemName: String? = null,
#field:SerializedName("status_refund")
val statusRefund: String? = null,
#field:SerializedName("detail_id")
val detailId: Int? = null,
#field:SerializedName("procode_new")
val procodeNew: String? = null,
#field:SerializedName("refund_date")
val refundDate: String? = null,
#field:SerializedName("request_refund")
val requestRefund: String? = null,
#field:SerializedName("procode")
val procode: String? = null,
#field:SerializedName("last_update")
val lastUpdate: String? = null,
#field:SerializedName("item_qty_new")
val itemQtyNew: Int? = null,
#field:SerializedName("order_id")
val orderId: String? = null,
#field:SerializedName("total_price_new")
val totalPriceNew: Int? = null,
#field:SerializedName("item_qty")
val itemQty: Int? = null,
#field:SerializedName("refund")
val refund: Int? = null
)
As you can see above, the body has attributes that are included in the data class as well. (This data class is also used in a related service in the App that uses the inputted data of this service).
And this is the function to trigger the POST request:
NetworkConfig().editOutstandingOrderProductDetailService().editProductDetail(
selectedOrderId,
selectedProcode,
selectedNip,
procodeNew,
itemNewQty,
itemPriceNew,
itemNewName,
totalPriceNew
).enqueue(object :
Callback<OutstandingOrderDetailPOJODataClassDetailItem> {
override fun onFailure(call: Call<OutstandingOrderDetailPOJODataClassDetailItem>, t: Throwable) {
Log.i("Order", "It Failed!!")
if (call.isCanceled) {
Toast.makeText((activity as AppCompatActivity), "Request Aborted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText((activity as AppCompatActivity), t.localizedMessage, Toast.LENGTH_SHORT).show()
}
}
override fun onResponse(
call: Call<OutstandingOrderDetailPOJODataClassDetailItem>,
response: Response<OutstandingOrderDetailPOJODataClassDetailItem>
) {
Log.i("Order", "Switching Process done!!!")
Log.i("Order", "Status: ${response.body()}")
}
})
From above, it prints the response in the logCat like this:
Am I missing something here? Or there's something I need to Change? If there's any detail that I missed to point out, just let me know!
Your request is a JSON object, not a formurl.
#Field tag is used when you want to pass your parameters as formurl
Use model class or JsonObject with #Body tag to pass parameters as JsonObject.
Model class Exmaple,
data class TestClass{
val text1: String,
val text2: String
}
Now Pass this class as request
#POST("URL")
fun apiName(#Body request: TestClass);
JSON Object Exmaple,
JSONObject paramObject = new JSONObject();
paramObject.put("key1", "value1");
paramObject.put("key1", "vaalue2");
Now pass it as String or JsonObject
#POST("URL")
fun apiName(#Body request: String); // pass as String
I am facing problem while sending json object body using retrofit to the server. Below is the error.
Failed to invoke public
com.nitesh.brill.saleslines._User_Classes.User_PojoClass.UpdatePreviousDetails()
with no args
code snippet
// Api endpoint
#Headers("Content-Type: application/json")
#POST("UpdatePreviousDetails/{Id}")
fun updatePreviousDetails(#Path("Id") Id: Int, #Body updateDetails :UpdatePreviousDetails): Call<UpdatePreviousDetails>
//pojo class
package com.nitesh.brill.saleslines._User_Classes.User_PojoClass
import java.util.*
/**
* Created by Nitesh Android on 16-08-2017.
*/
class UpdatePreviousDetails(
var CompanyName: String? = null!!,
var Designation: String? = null!!,
var DateOfJoin: Date? = null!!,
var DateOfLeaving: Date? = null!!,
var SectorPreviouslyWorked: String? = null!!,
var Id: Int? = null!!
) {
}
//sending data
val details = UpdatePreviousDetails("rr", "asm", date, date, "Pharmaceuticals",3)
val call = apiEndpointInterface!!.updatePreviousDetails(5, details)
call.enqueue(object :Callback<UpdatePreviousDetails> {
override fun onResponse(call: Call<UpdatePreviousDetails>?, response: Response<UpdatePreviousDetails>?) {
objUsefullData.showSnackBar("success")
UsefullData.Log("============="+response!!.body().toString())
}
override fun onFailure(call: Call<UpdatePreviousDetails>?, t: Throwable?) {
objUsefullData.showSnackBar("fail")
UsefullData.Log("============="+t)
}
})
I am using kotlin language
Your UpdatePreviousDetails class has to have a constructor with no params to enable Gson (inside Retrofit) to convert your object into JSON.
EDIT
class UpdatePreviousDetails() {
var CompanyName: String? = null
var Designation: String? = null
var DateOfJoin: Date? = null
var DateOfLeaving: Date? = null
var SectorPreviouslyWorked: String? = null
var Id: Int? = null
constructor(
CompanyName: String?,
Designation: String?,
DateOfJoin: Date?,
DateOfLeaving: Date?,
SectorPreviouslyWorked: String?,
Id: Int?
) : this() {
this.CompanyName = CompanyName
this.Designation = Designation
this.DateOfJoin = DateOfJoin
this.DateOfLeaving = DateOfLeaving
this.SectorPreviouslyWorked = SectorPreviouslyWorked
this.Id = Id
}
}