How to handle map<dynamic> null object when api is called - android

How can I manage server response if status is either than 200.
#JsonSerializable(nullable: false)
class LoginResponse {
final String error;
final int status;
final List<User> userList;
LoginResponse({this.error, this.status, this.userList});
factory LoginResponse.fromJson(Map repJson){
List<dynamic> userListResp=repJson['userData'];
List<User> userList=userListResp.map((e)=>User.fromUser(e)).toList();
int s=repJson['status'];
if(s==200){
return LoginResponse(error:repJson['error'],status: repJson['status'],userList:userList);
} else{
return LoginResponse(error:repJson['error'],status: repJson['status']);
}}}
class User{
String cust_id;
String cust_name;
String cust_email;
String cust_mob;
User({this.cust_id,this.cust_name,this.cust_email,this.cust_mob});
factory User.fromUser(Map userJson){
return User(cust_id: userJson['cust_id'],cust_name: userJson['cust_name'],
cust_email: userJson['cust_email'],cust_mob: userJson['cust_mob']);
}
}
server response when an error is occur
{"error":"1","status":201,"message":"Entered email id already exist in our records"}
server response on success
{
"error":"0",
"status":200,
"userData":[
{
"cust_id":"87",
"cust_name":"kio",
"cust_email":"kio1#kio.com",
"cust_gend":null,
"cust_dob":null,
"cust_mob":"098998899889588",
"cust_pass":"e10adc3949ba59abbe56e057f20f883e",
"cust_age":null,
"device_type":"android",
"device_token":"eNWqzDwxqsQ:APA91bF-uK1MI11D3SgHGSw7Omv1imjDrPKBBCrN9JgmyJppHsNVeG5l56EkCCd5ZMaxL_ehQzVhtoEj0fTNB55wYGJt5BqYVvwfAb7HrBqwb_21M6VFPuF6LQINkvE1offQgZYweROO",
"status":"0",
"createAt":"2019-01-31 18:45:19",
"updatedAt":"0000-00-00 00:00:00",
"login_type":"",
"login_id":null,
"is_guest":"0",
"auth_token":"",
"forgot_token":null
}]
}
How can I manage when user data is not present or null, I tried to manage when the status code is 201 but still showing
NoSuchMethodError: The method 'map' was called on null.

To fix your code move the userList mapping inside the if block. This way you will parse the response only of the status code is 200.
int s=repJson['status'];
if (s==200) {
List<dynamic> userListResp=repJson['userData'];
List<User> userList=userListResp.map((e)=>User.fromUser(e)).toList();
return LoginResponse(error:repJson['error'], status:repJson['status'], userList:userList);
} else {
return LoginResponse(error:repJson['error'], status:repJson['status']);
}
However, you might not want to handle errors in your model. It is better to check for error after you performed the request and then decide if you want to parse the response.
Something like this will be easier to handle and won't pollute your model object:
final response = await client.get(requestUrl);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
final loginResponse = LoginResponse.fromJson(json.decode(response.body));
// ...
} else {
// If that call was not successful, throw an error or parse the error object.
throw Exception('Failed to login');
// ...
}

Related

Not able to get data from retrofit

I am using retrofit to pass login and register api in android. But I am getting response as 409 in return. I am not getting data from api. Retrofit 2 is used here
SignUpApi signupapi = Api_Config.getInstance3().getApiBuilder().create(SignUpApi.class);
Call<SignUpApi.ResponseSignUp> call = signupapi.POSTDATA(UserName.getText().toString().trim(),
Email.getText().toString().trim(),
Password.getText().toString().trim(),
Sex.getText().toString().trim(),
Mobile.getText().toString().trim());
call.enqueue(new Callback<SignUpApi.ResponseSignUp>() {
#Override
public void onResponse(Call<SignUpApi.ResponseSignUp> call, Response<SignUpApi.ResponseSignUp> response) {
CustomProgressDialog.getInstance().dismiss();
if (response.isSuccessful()){
Log.e("Status is",response.body().getStatus().toString());
if (response.body().getStatus() == 200){
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,Constants.SuccessfullyRegistered);
CommonFunctions.getInstance().FinishActivityWithDelay(SignInActivity.this);
}else if (response.body().getStatus() == 409){
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,Constants.YouAreAlreadyRegistered);
}else{
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,response.body().getMsg());
}
} else {
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,Constants.SomethingWentWrong);
}
}
#Override
public void onFailure(Call<SignUpApi.ResponseSignUp> call, Throwable t) {
CustomProgressDialog.getInstance().dismiss();
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,t.getMessage());
}
});
}
Below is my API configuration
public static Api_Config getInstance3()
{
if (ourInstance == null){
synchronized (Api_Config.class){
if ( ourInstance == null )
ourInstance = new Api_Config();
}
}
ourInstance.config3();
return ourInstance;
}
private void config3() {
Gson gson = new GsonBuilder()
.setLenient()
.create();
String BASE_URL3 = LOGIN_AND_SIGNUP;
mRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL3)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
Below is my Api Class
public interface SignUpApi {
#FormUrlEncoded
#POST("register.php")
Call<ResponseSignUp> POSTDATA(#Field("user_name")String username,
#Field("user_email")String email,
#Field("user_password")String password,
#Field("user_gender")String sex,
#Field("user_mobile")String mobile
);
public class ResponseSignUp
{
#SerializedName("status")
#Expose
private Integer status;
#SerializedName("msg")
#Expose
private String msg;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
I am newbee to android and really confused why my code is not working. Looking for help. Thanks in Advance
Note that if your response was successfully it means you got a successful code (200...300). However, if you get a response 401, 409.. it means you got an error, then your response was not successfully. Put the error handle outside the response.isSuccessful() condition.
if (response.isSuccessful()){
//Handle success response here
Log.e("Status is",response.body().getStatus().toString());
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,Constants.SuccessfullyRegistered);
CommonFunctions.getInstance().FinishActivityWithDelay(SignInActivity.this);
} else {
// Handle error response here, 401, 409...
if (response.body().getStatus() == 409){
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,Constants.YouAreAlreadyRegistered);
}else{
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,response.body().getMsg());
}
}
You can check this in the retrofit2 files.
/** Returns true if {#link #code()} is in the range [200..300). */
public boolean isSuccessful() {
return rawResponse.isSuccessful();
}
EDIT
Depper explanation
I'll try to explain better. When you call retrofit using enqueue method like this: call.enqueue() you expect to get a Response or Failure from the server: onResponse() means you got a response and onFailure() means you failed to connect to the server, it could mean the server is broken or there is no internet connection.
If you got a onResponse() from the server it does not mean it was successful, it just means you got a response, therefore you need to check if this response was successful or not by using this condition
if (response.isSuccessful){
}
What is a successful response?
If you end up inside this condition response.isSuccessful it already means you got a successful response and this is a response with code between 200 and 300.
However, if you want to check if you got a 409 code. 409 code means that it was a unsuccessful response, then you need to check this outside the success condition.
if (response.isSuccessful){
// You got a successful response, the code is from 200 to 300.
} else {
// You got a unsuccessful response, handle the code 401, 405, 409 here.
}

How to send a POST request from RestSharp on android to a WebAPI server

I have an android app that needs to make a call to a asp.net core web api server.
I am using RestSharp to make the request.
Here is the code generating the request:
public LoginResponse SignInWithGoogle(string token)
{
//Api request for token
RestRequest request = new RestRequest("login/google", Method.POST);
request.AddJsonBody(new { Token = token });
//request.AddParameter("token", token, ParameterType.GetOrPost);
var response = restClient.Execute<LoginResponse>(request);
if (response.ErrorException != null)
{
throw new Exception("The APi request failed. See inner exception for more details", response.ErrorException);
}
AuthenticationToken = response.Data.token;
restClient.Authenticator = authenticator;
return response.Data;
}
Here is the web api code:
[AllowAnonymous]
[HttpPost]
[Route("google")]
public IActionResult GoogleLogin([FromBody] GoogleLoginDto data)
{
GoogleJsonWebSignature.Payload payload;
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SigningCredentials creds = new SigningCredentials(Global.symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
//Try to validate the Google token.
try
{
payload = GoogleJsonWebSignature.ValidateAsync(data.Token).Result;
}
catch (Exception e)
{
return Unauthorized();
}
...
}
GoogleLoginDto contains one property Token that is public.
The problem is that I get a 404. It seems to me that the JSON in the request is not being serialized to GoogleLoginDto but I can't find out why... I'm guessing because the API can't find the data field and so thinks i'm asking for a route that doesn't exist...
I also tried doing request.AddParameter("token", token, ParameterType.GetOrPost); as you can see, but I get an exception saying that Content-Type can't be null.
I thought about adding the Content-Type header but that seems ridiculous because RestSharp is supposed to determine that automatically...
Can anyone see anything I'm missing here? Thanks.
The API is unable to map the provided URL to a controller action. That is what it is 404 Not Found. Nothing to do with the data. It is the URL.
Given that the desired URL is login/google, ensure that the target controller has the proper routes defined that would allow the request to be mapped to the correct actions.
[Route("login")] // Route prefix
public class LoginController : Controller {
[AllowAnonymous]
[HttpPost("google")] // Matches POST login/google
public async Task<IActionResult> GoogleLogin([FromBody] GoogleLoginDto data) {
if(ModelState.IsValid) {
GoogleJsonWebSignature.Payload payload;
var tokenHandler = new JwtSecurityTokenHandler();
var creds = new SigningCredentials(Global.symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
//Try to validate the Google token.
try {
payload = await GoogleJsonWebSignature.ValidateAsync(data.Token);
} catch (Exception e) {
return Unauthorized();
}
return Ok();
}
return BadRequest();
}
}

Check which type of data is coming as response using Retrofit

As I'm using Retrofit, I've designed all the POJOs and it was working flawlessly. API is designed in such a way that it will send the required data if the data is of current date or of future dates but not for past dates. In the response, I'll get a JSON response contains a combination of JSON objects and an array as a value of a JSON object and POJOs are according to that. Now if there is no record for present and future dates then I'll receive a string instead of an array and that leads to API error java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING. So what I wanna know if there is any way that I can determine - what I'm receiving an array or a string? and how to update POJO according to that to avoid that error.
JSON response when server has no data
{
"Result_Code": "ND",
"Result_Status": "Success",
"Result_Message": "No record found in database.",
"Result_Output": "",
"updatedDate": "20-07-2017 10:44:37"
}
JOSN response will be same when server has data but with one difference
{
"Result_Code": "ND",
"Result_Status": "Success",
"Result_Message": "record found in database.",
"Result_Output": [{multiple elements},
{multiple elements},
{multiple elements}....],
"updatedDate": "20-07-2017 10:44:37"
}
Pojo class named ResponseModel
public class ResponseModel {
private String Result_Code;
private String Result_Status;
private String Result_Message;
private Object Result_Output;
private String updatedDate;
...
}
using Object you can morph as below
call.enqueue(new Callback<ResponseModel>()
{
#Override
public void onResponse(Response<ResponseModel> response, Retrofit retrofit)
{
parseData(); // get other data from ResponseModel Class
if (response.getResultOutput() instanceof List<POJO>)
{
doSomething();
}
else if (response.getResultOutput() instanceof String)
{
doSomething();
}
else //must be error object
{
doSomething();
}
}
#Override
public void onFailure(Throwable t)
{
///Handle failure
}
});
using instanceof you check desired Object type
Where List<POJO> used for multiple elements Model
*check updated solution for parsing ArrayList from response object
hopefully it might be work as you want
catch your json in debug mode and generate pojo class with link below. then compare your class and see difference
http://www.jsonschema2pojo.org/
you can try this method.
try {
callArrayPojo();
} catch (IllegalStateException e) {
callStringPojo();
} catch (Exception e) {
//other}
or you can get ResultMessage generic type
...
private String Result_Code;
private String Result_Status;
private T Result_Message;
...

Retrofit / OkHttp3 400 Error Body Empty

I am using Retrofit 2 in my Android project. When I hit an API endpoint using a GET method and it returns a 400 level error I can see the error content when I use an HttpLoggingInterceptor, but when I get to the Retrofit OnResponse callback the error body's string is empty.
I can see that there is a body to the error, but I can't seem to pull that body when in the context of the Retrofit callback. Is there a way to ensure the body is accessible there?
Thanks,
Adam
Edit:
The response I see from the server is:
{"error":{"errorMessage":"For input string: \"000001280_713870281\"","httpStatus":400}}
I am trying to pull that response from the response via:
BaseResponse baseResponse = GsonHelper.getObject(BaseResponse.class, response.errorBody().string());
if (baseResponse != null && !TextUtils.isEmpty(baseResponse.getErrorMessage()))
error = baseResponse.getErrorMessage();
(GsonHelper is just a helper which passes the JSON string through GSON to pull the object of type BaseResponse)
The call to response.errorBody().string() results in an IOException: Content-Length and stream length disagree, but I see the content literally 2 lines above in Log Cat
I encountered the same problem before and I fixed it by using the code response.errorBody().string() only once. You'll receive the IOException if you are using it many times so it is advised to just use it as a one-shot stream just as the Documentation on ResponseBody says.
My suggestion is: convert the Stringified errorBody() into an Object immediately as the latter is what you're gonna be using on subsequent operations.
As it was mentioned, you need to use response.errorBody().string() only once. But there is a way to get the error body string more than once.
TL;DR Use the code below to get error body string from response more than once.
public static String getErrorBodyString(Response<?> response) throws IOException {
// Get a copy of error body's BufferedSource.
BufferedSource peekSource = response.errorBody().source().peek();
// Get the Charset, as in the original response().errorBody().string() implementation
MediaType contentType = response.errorBody().contentType(); //
Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
charset = Util.bomAwareCharset(peekSource, charset);
// Read string without consuming data from the original BufferedSource.
return peekSource.readString(charset);
}
Explanation:
This is based on the original response.errorBody().string() method implementation. It uses the copy of BufferedSource from peek() and returns the error body string without consuming it, so you can call it as many times as you need.
If you look at the response.errorBody().string() method implementation, you'll see this:
public final String string() throws IOException {
try (BufferedSource source = source()) {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
}
}
source.readString(charset) consumes data of the error body's BufferedSource instance, that's why response.errorBody().string() returns an empty string on next calls.
To read from error body's BufferedSource without consuming it we can use peek(), which basically returns a copy of the original BufferedSource:
Returns a new BufferedSource that can read data from this
BufferedSource without consuming it.
you can use Gson to get errorBody as your desired model class:
val errorResponse: ErrorMessage? = Gson().fromJson(
response.errorBody()!!.charStream(),
object : TypeToken<ErrorMessage>() {}.type
)
First create an Error class like below:
public class ApiError {
#SerializedName("httpStatus")
private int statusCode;
#SerializedName("errorMessage")
private String message;
public ApiError() {
}
public ApiError(String message) {
this.message = message;
}
public ApiError(int statusCode, String message) {
this.statusCode = statusCode;
this.message = message;
}
public int status() {
return statusCode;
}
public String message() {
return message;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
}
Second you can create a Utils class to handle your error like below:
public final class ErrorUtils {
private ErrorUtils() {
}
public static ApiError parseApiError(Response<?> response) {
final Converter<ResponseBody, ApiError> converter =
YourApiProvider.getInstance().getRetrofit()
.responseBodyConverter(ApiError.class, new Annotation[0]);
ApiError error;
try {
error = converter.convert(response.errorBody());
} catch (IOException e) {
error = new ApiError(0, "Unknown error"
}
return error;
}
And finally handle your error like below:
if (response.isSuccessful()) {
// Your response is successfull
callback.onSuccess();
}
else {
callback.onFail(ErrorUtils.parseApiError(response));
}
I hope this'll help you. Good luck.
If you are gettig 400 then its a bad request you r trying to send to server.
check your get req.

Using Retrofit with Restfull Post WebServices

I'm trying to use Retrofit with Restful WebService. Everything seems alright, but somehow when I run this code this will always returns this
Method not found. Retrofit 404 Error
Here is my WebServices Code
public function processApi() {
$func = strtolower(trim(str_replace("/","",$_POST['request'])));
if ((int)method_exists($this,$func) > 0) {
$this->$func();
} else {
// If the method not exist with in this class, response would be "Page not found".
$this->response('Method not found',404);
}
}
private function login() {
// Cross validation if the request method is POST else it will return "Not Acceptable" status
if ($this->get_request_method() != "POST") {
// If invalid inputs "Bad Request" status message and reason
$error = array('status' => "0", "msg" => "Bad Request");
$this->response($this->json($error), 406);
}
// Input validations
if (empty($email) and empty($password)) {
$error = array('status' => "0", "msg" => "Invalid Email address or Password");
$this->response($this->json($error), 400);
}
}
public class ObjectPost {
#SerializedName("request")
String request;
#SerializedName("email")
String event_id;
public void setRequest(String request) {
this.request = request;
}
public void setEvent_id(String event_id) {
this.event_id = event_id;
}
}
And here is my Android Request Code
public class RestClient {
public interface ClientInterface {
#POST(Config.LOGIN_URL)
void login(#Body ObjectPost mObject,
Callback<LoginBeans> callback);
}
public static ClientInterface initRestAdapter() {
OkHttpClient client = new OkHttpClient();
return (ClientInterface) new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setClient(new OkClient(client))
.setEndpoint(Config.SERVER_URL)
.build()
.create(ClientInterface.class);
}
}
The value in the
Config.LOGIN_URL
Is most likely incorrect. Please remember that
Config.SERVER_URL
Must contain the base URL address. e.g. http://www.server.com/ (also please note the slashes are important)
Next, what is in your attribute must be only the remainder of the specific method that would be appended on that base url. e.g. if the method you want to call is login, it should be
#POST("/login")
Once again, I am not kidding about the slashes.
Also remember that if a query parameter is sent through as null, retrofit ignores it (you may face this problem later).
If you need any further help, you already have your loglevel set to full, please add the logcat to your question so we can see what is happening.
Your code looks alright.
Basically you need to make sure your backend. Ensure that the controller or whatever is actually right.
Maybe this will be quite applicable to you https://github.com/square/retrofit/issues/789
So, instead of looking at your Android code, it's a good reason to look somewhere else I would say.

Categories

Resources