I am using Firebase REST API for my auth logic. I am using MVVM pattern for my project. I can get two different types of responses. One of them successful one of them is error. I get successful response only email and password correct. Other times I get error response. I can handle successful Response but I couldn't handle error response.
Here is my example Error Response which I get from Postman :
{
"error": {
"code": 400,
"message": "INVALID_PASSWORD",
"errors": [
{
"message": "INVALID_PASSWORD",
"domain": "global",
"reason": "invalid"
}
]
}
}
And this is successful response
{
"kind": "identitytoolkit#VerifyPasswordResponse",
"localId": "c5Tbai1nG7U****",
"email": "muratcan*****",
"displayName": "",
"idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjQ4OTQ5ZDdkNDA3ZmVjOWIyYWM4ZDYzNWVjYmEwYjdhOTE0ZWQ4ZmIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3V****",
"registered": true,
"refreshToken": "AOvuKvSHOlnUg9O94OvADakpm5r7hXLP32k6WdBqIfTiLZHq-MVXnQcegBrRY3Pd1UjtO4WOTVv4****",
"expiresIn": "3600"
}
I can access successful response like email , registered , idToken etc.
But I want to get message from error response. Which is "message": "INVALID_PASSWORD" ( for this example of course it can get another values like Email not found etc.)
This is my Response Class.
data class SignInWithEmailResponse(
#SerializedName("kind")
var kind: String = "",
#SerializedName("localId")
var localId: String = "",
#SerializedName("email")
var email: String = "",
#SerializedName("displayName")
var displayName: String = "",
#SerializedName("idToken")
var idToken: String = "",
#SerializedName("registered")
var registered: Boolean = false,
#SerializedName("refreshToken")
var refreshToken: String = "",
#SerializedName("expiresIn")
var expiresIn: String = ""
)
And here is my login method.
class LoginScreenViewModel(application: Application) : AndroidViewModel(application) {
//some other declarations which are not important for this question
fun login(email: String, password: String) {
if (isEmailValid(email) && isPasswordValid(password)) {
loginAuth(email, password)
} else {
loginSuccessLiveData.postValue(false)
}
}
private fun loginAuth(username: String, password: String) {
val loginService = ServiceGenerator.createService(LoginService::class.java, username, password)
val call: Call<Any> = loginService.loginWithEmail(SignInWithEmailRequest(username, password))
call.enqueue(object : Callback<Any> {
override fun onResponse(
call: Call<Any>,
response: Response<Any?>
) {
if (response.isSuccessful) {
loginSuccessLiveData.postValue(true)
val gsonVal = Gson().toJson(response.body())
val deGsonVal = Gson().fromJson(gsonVal, SignInWithEmailResponse::class.java)
Log.d("response", deGsonVal.toString()) // I can get my values correctly
} else {
loginSuccessLiveData.postValue(false)
val gsonVal = Gson().toJson(response.errorBody())
val deGsonVal = Gson().fromJson(gsonVal, SignInWithEmailError::class.java)
Log.d("response", deGsonVal.message) // application crashes
}
}
override fun onFailure(call: Call<SignInWithEmailResponse>, t: Throwable) {
//connectivity Error handle
}
})
}
}
And this is my response. I get this data with debugging. But I can't read that data. I mark it with red.
Here is my Login service interface
interface LoginService {
#POST(postValue) //api end point is declared outside of class. Not important for this question
fun loginWithEmail(#Body request:SignInWithEmailRequest): Call<SignInWithEmailResponse>
}
I also tried to create a class like SignInWithEmailResponse. It's name is SignInWithEmailError. I put a String variable in it which name is message. But I couldn't put this in my code. I use this lines for my calls.
call: Call<SignInWithEmailResponse>,
response: Response<SignInWithEmailResponse?>
I couldn't figure out how to implement SignInWithEmailError class anywhere in this call.
UPDATE
Here is the Error Response class.
data class SignInWithEmailError(
#SerializedName("errors")
var errors: Errors = Errors(),
#SerializedName("code")
var code: Int = 0,
#SerializedName("message")
var message: String =""
)
Related
I'm trying to parse a JSON without results.
I've a response like following:
{
"0": {
"id": "4",
"nome": "Zero Gravity",
"id_stop": "0"
},
"1": {
"id": "540",
"nome": "First Name",
"id_stop": "111"
}
}
The problem is that it's not a list with a defined id, but the id is a progressive integer that varies according to the possible answers. How do I do proper parsing? I tried building a response like this:
here my service
#GET("{path}")
suspend fun getResponse(
#Path(value = "path", encoded = true) path: String,
#Query(value = "cmd") alias: String? = "cmd",
#Query(value = "id_where") idWhere: String,
): Response<ArrayList<APIResponse>>
Here APIResponse
data class APIResponse(
#JsonProperty("id")
val id: String?,
#JsonProperty("nome")
val nome: String?,
#JsonProperty("id_stop")
val idStop: String?,
) {
fun toDomain(): API {
return API( id, nome, idStop
)
}
}
here my repository
suspend fun getAPIResponse(from: String) : API {
val response = service.getResponse(path = basePath, idWhere = where)
return response.body()?.map {
it.toDomain()
}
}
This isn't the solution though, because I can't get a complete answer but I always have a single item with all fields null. Should I use a HashMap? how could i solve?
I am making an android application with kotlin.
I am facing the following error when calling the api.
{"status":400,"errorMessage":"request body malformed."}
I think the message part is the problem.
I gave the variables messages1 and messages2 values to messages, but to no avail. (RetrofitService.sendsms value was also changed to match the type)
please help me
Here is the api guide.
API Url & API Header
Request body
Response body
Next is the code I used.
<MainActivity.kt>
var messages1 = listOf(messages("0"+PhoneNum_to))
var messages2 = """{"to": "${"0"+PhoneNum_to}"}"""
timestamp = System.currentTimeMillis().toString()
smsservice.sendsms(
Content_Type = "application/json; charset=UTF-8",
timestamp = timestamp,
accesskey = "{accesskey}",
signature = "{signKey}",
type = "LMS",
contentType = "COMM",
from = "{Caller number}",
content = "{message content}",
messages = messages1
)?.enqueue(object : Callback<sendsms_Response> {
override fun onResponse(call: Call<sendsms_Response>, response: Response<sendsms_Response>) {
if(response.isSuccessful){
// true
} else{
// false
}
override fun onFailure(call: Call<sendsms_Response>, t: Throwable) {
// Exception
Log.d("NaverSMSApiResult", "onFailure 에러: " + t.message.toString());
}
})
<RetrofitService.kt>
interface RetrofitService {
// POST 예제
#FormUrlEncoded
#POST("/sms/v2/services/{Service_ID}/messages")
fun sendsms(
#Header("Content-Type") Content_Type: String,
#Header("x-ncp-apigw-timestamp") timestamp: String,
#Header("x-ncp-iam-access-key") accesskey: String,
#Header("x-ncp-apigw-signature-v2") signature: String,
#Field("type") type: String,
#Field("contentType") contentType: String,
#Field("from") from: String,
#Field("content") content: String,
#Field("messages") messages : List<messages>
): Call<sendsms_Response>
}
data class messages(
var to : String,
var subject : String,
var content : String,
) {
constructor(Number : String) : this(Number, "null", "null")
}
<sendsms_Response.kt>
// DTD Create
data class sendsms_Response(
#SerializedName("statusCode") var statusCode: String,
#SerializedName("statusName") var statusName: String,
#SerializedName("requestId") var requestId: String,
#SerializedName("requestTime") var requestTime: String,
)
The error is caused because your message data cannot be recognized as a JSON and has a problem in the body. Try to fix it in a correct JSON form and I think that will solve your problem.
also you can check out this link for more info:
https://www.ktmbytes.com/send-sms-from-android-app-using-nexmo-and-retrofit
I am making generic classes for hitting otp api.anybody can use otp section just have to pass request ,Response class and url and all will be done by this otp section.
Please note : this response class can be of different type (for eg: MobileOtpResponse,EmailOtpResponse)
below is the generic OtpClient which takes any request type and returns particular passed ResponseType (for example : Request class passed is OtpRequest ,ResponseType class passed is OtpResponse)
interface OtpClient {
#POST
suspend fun <Request : Any, ResponseType> sendOtp(#Url url: String,
#Body request:#JvmSuppressWildcards Any): #JvmSuppressWildcards ResponseType
}
OtpRequest
data class OtpRequest(#SerializedName("mobile_number") val mobileNumber: String,#SerializedName("app_version") val appVersion: String)
OtpResponse
data class OtpResponse(#SerializedName("status") val status: String = "",
#SerializedName("response") val response: OtpData? = null)
data class OtpData(
#SerializedName("otp_status") val otpStatus: Boolean = false,
#SerializedName("message") val message: String = "",
#SerializedName("error") val error: Int? = null,
#SerializedName("otp_length") val otpLength: Int? = null,
#SerializedName("retry_left") val retryLeft: Int? = null,)
Now i create Repo to call this api this simply use flow and when the data fetch it emits the data
class OtpRepoImpl<out Client : OtpClient>(val client: Client) :OtpRepo {
override fun <Request:Any, ResponseType> sentOtpApi(url: String, request: Request): Flow<ResponseType> {
return flow<ResponseType> {
// exectute API call and map to UI object
val otpResponse = client.sendOtp<Request, ResponseType>(url,request)
emit(otpResponse)
}.flowOn(Dispatchers.IO) // Use the IO thread for this Flow
}
}
this repo is used in viewmodel class
#ExperimentalCoroutinesApi
fun <A : Class<ResponseType>, Request : Any, ResponseType : Any> sendOtp(a: Class<ResponseType>, request: Request, response: ResponseType, url: String) {
viewModelScope.launch {
repo.sentOtpApi<Request, ResponseType>(url, request = request)
.onStart { _uiState.value = OtpState.Loading(true) }
.catch { cause ->
_uiState.value = OtpState.Loading(false)
getResponseFromError<Class<ResponseType>,ResponseType>(cause, response) {
// emit(it)
}
}
.collect {
_uiState.value = OtpState.Loading(false)
_uiState.value = OtpState.Success(it)
}
}
}
as you can see above this sendOtp method is called from the view class and inside this method we use repo.sentOtpApi and pass generic request response type.I get data in catch block coz api is send error otp data in 400 HttpException so i created another method getResponseFromError to get error response it should parse the errorBody response and call this lambda block.
private suspend fun <A : Class<*>, ResponseType : Any> getResponseFromError( cause: Throwable, rp: ResponseType, block: suspend (ResponseType) -> Unit) {
if (cause is HttpException) {
val response = cause.response()
if (response?.code() == 400) {
println("fetching error Response")
val errorResponse = response.errorBody()?.charStream()
val turnsType = object : TypeToken<ResponseType>() {}.type
val finalErrorResponse = Gson().fromJson<ResponseType>(errorResponse, turnsType)
block(finalErrorResponse)
} else {
println("someOther exception")
}
} else
_uiState.value = OtpState.Error(cause)
}
so here i am facing the problem inside above method
val turnsType = object : TypeToken<ResponseType>() {}.type
val finalErrorResponse = Gson().fromJson<ResponseType>(errorResponse, turnsType)
block(finalErrorResponse)
This finalErrorResponse is returning LinkedTreeMap instead of ResponseType (in this case its OtpResponse)
i have also tried using Class<*> type like this
val turnsType = object : TypeToken<A>() {}.type
val finalErrorResponse = Gson().fromJson<A>(errorResponse, turnsType)
but its not working.
calling of this sentOtp viewmodel func is like
var classType = OtpResponse::class.java
otpViewModel.sendOtp(a = classType, request = otpRequest, response = OtpResponse() , url =
"http://preprod-api.nykaa.com/user/otp/v2/send-wallet-otp")
[![value in finalErroResponse][1]][1]
[1]: https://i.stack.imgur.com/Holui.png
required: finalErroResponse should be of OtpResponse type because that was passed in sentOtp func
Please help :)
I want to send data request via post to server I want to know How can I add data in array
data class City(
#SerializedName("cityId")
val cityId: Int?,
#SerializedName("detail")
val detail: List<String?>
)
Request
data class CityRequest(
#SerializedName("listCity")
val listCity: List<City?>
)
Response
data class CityResponse(
#SerializedName("code")
val code: String?,
#SerializedName("status")
val status: Boolean?,
#SerializedName("message")
val message: String?
)
API Server
#Headers("Content-Type: application/json")
#POST("city")
suspend fun sendCityContent(#Body listCity: CityRequest?):
Call<CityResponse?>
Connect Service
I don't know how I can add information to this section in question.
private suspend fun sendDataCity(city: List<city?>) {
val retrofit = clientCity
val sendDataToServer = retrofit?.create(CityService::class.java)
val call = sendDataToServer?.sendCityContent(CityRequest(city))
call?.enqueue(object : Callback<CityResponse?> {
override fun onResponse(
call: Call<CityResponse?>, response: Response<CityResponse?>) {
val getResponse = response.body()
Timber.tag("SALE_CITY: ").d("code: %s", getResponse?.code)
Timber.tag("SALE_CITY: ").d("status: %s", getResponse?.status)
Timber.tag("SALE_CITY: ").d("message: %s", getResponse?.message)
}
override fun onFailure(call: Call<CityResponse?>, t: Throwable?) {
t?.printStackTrace()
}
})
}
JSON Simple
{
"city": [
{
"cityId": 1,
"answer": [
"1"
]
},
{
"questionId": 2,
"answer": [
"2.1",
"2.2"
]
}
]}
What do I have to do next?
Can you have a sample add data in array for me?
Things I want
cityId = 1
detail = "1.1", "1.2"
cityId = 2
detail = "2.1", "2.2"
thank you
One issue i can see with your request is the key is different from what you are sending might be different check that. it should be city not listCity as given.
data class CityRequest(
#SerializedName("city")
val city: List<City?>
)
and your city class should have these keys answer which you have mentioned as details
data class City(
#SerializedName("cityId")
val cityId: Int?,
#SerializedName("answer")
val answer: List<String?>
)
I guess you are just sending with wrong keys that might be the reason the server is not accepting the request. make the above change it should work post if you get error.
I am new in Android development, and I am trying to get data from server. the general JSON response structure will be like this
{
"success": "1",
"data": [
{
"customers_id": 4,
"customers_gender": "0",
"customers_firstname": "TES IOS",
"customers_lastname": "TES IOS",
"customers_dob": "2018-12-27",
"email": "TES002#email.com",
"user_name": "TES002",
"customers_default_address_id": 0,
"customers_telephone
},
"message": "Successfully get user data from server"
}
the "success" and "message" field will be the same (will always be string). but the "data" can be different for other request call. It can send user data, store data or product data, or even Array/List of Products.
so I want to make general reusable class to catch that JSON response. the class will be like this, I set the "data" to be Any, and then later it will be casted back to User object:
class ServerData(successStatus: Int, data: Any, message: String) {
val isSuccessfull : Boolean
val data : Any
val message : String
init {
isSuccessfull = successStatus != 0
this.data = data
this.message = message
}
}
the interface is like this:
interface LakuinAPI {
#FormUrlEncoded
#POST("processlogin")
fun performLogin(
#Field("kode_customer") outletCode: String,
#Field("password") password: String
): Call<ServerData>
}
and then I use it in the activity, like the code below:
private fun sendLoginDataToServer(outletCode: String, password: String) {
val call = lakuinAPI.performLogin(outletCode,password)
call.enqueue(object: Callback<ServerData> {
override fun onFailure(call: Call<ServerData>, t: Throwable) {
Toast.makeText(this#LoginActivity,t.localizedMessage,Toast.LENGTH_LONG).show()
}
override fun onResponse(call: Call<ServerData>, response: Response<ServerData>) {
if (!response.isSuccessful) {
Toast.makeText(this#LoginActivity,"Code: " + response.code(),Toast.LENGTH_LONG).show()
return
}
val lakuinServerData = response.body()
val userList = lakuinServerData?.data as List<User> // the error in here
val userData = userList.first() // the error in here
println(userData)
}
})
}
but I get error message:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap
cannot be cast to com.ssss.lakuinkotlin.Model.User
I give comment in the code above the location of the error. I don't why it happened.
to be honest, I am not if this is the correct way to catch user data from general response JSON like the the JSON above. is there a better way ?
You can use generics to achieve it
class Response<Data> constructor() : ResponseSimple() {
#SerializedName(FIELD_DATA)
var data: Data? = null
private constructor(data: Data) : this() {
this.data = data
}
companion object {
const val FIELD_SUCCESS = "success"
const val FIELD_ERROR = "error"
const val FIELD_DATA = "data"
const val FIELD_MESSAGE = "message"
#JvmStatic
fun <Data> create(data: Data): Response<Data> {
return Response(data)
}
}
}
And ResponseSimple is
open class ResponseSimple {
#SerializedName(Response.FIELD_ERROR)
var error: String = ""
#SerializedName(Response.FIELD_SUCCESS)
var succes: Boolean = false
#SerializedName(Response.FIELD_MESSAGE)
var message:String = ""
}
Then api response should be Call<Response<ServerData>>.
And about ClassCastException, you can't convert ServerData to User just using as.
You need to use Call<Response<ArrayList<User>>> or create class converter.
Try replacing this line :
val userList = lakuinServerData?.data as List<User>
with:
val userList = lakuinServerData?.data as new TypeToken<List<User>>(){}.getType()