Android, Xamarin, Rest - how to pass object in the request - android

I have a simple server rest endpoint running Spring -
#RestController
#RequestMapping("/services")
#Transactional
public class CustomerSignInService {
#Autowired
private CustomerDAO customerDao;
#RequestMapping("/customer/signin")
public Customer customerSignIn(#RequestParam(value = "customer") Customer customer) {
//Some Code Here...
return customer;
}
}
I'm trying to pass a Customer object from my Xamarin Android App using this method -
public JsonValue send(String url, SmartJsonSerializer obj)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(url));
request.ContentType = "application/json";
request.Method = "POST";
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(obj.toJsonString());
}
using (WebResponse response = request.GetResponse())
{
using (Stream stream = response.GetResponseStream())
{
return JsonObject.Load(stream);
}
}
}
But i keep getting Bad Request Exception (Http Error 400) and obviously my code at the server side is not triggered.
SmartJsonSerializer uses JSON.NET to serialize the Customer object to string -
using System;
using Newtonsoft.Json;
namespace Shared
{
public class SmartJsonSerializer
{
public string toJson()
{
return JsonConvert.SerializeObject(this);
}
}
}
Any help appreciated,
thnx!

Typically if you are posting a complex object to an api like this, you would write it in the request body. You do appear to be doing this on the android side.
I am not familiar with Spring, but it looks that you are expecting customer as a url parameter - Try replacing #RequestParam with #RequestBody.

I was struggling with it for a while, but apparently the solution may be find in the server side.
If it helps, You can look at this
#RestController
#RequestMapping("/services")
#Transactional
public class SomeService {
#RequestMapping(value = "/user/signin", method = RequestMethod.POST)
#ResponseBody
public AppUser signIn(#RequestBody AppUser appUser) {
appUser.invoke();
return appUser;
}
}

Related

Retrofit call returning 400, cURL request working perfectly fine, syntax issue

I've tried making a retrofit call to an API endpoint, but it's returning a 400 error, however my curl request is working perfectly fine. I can't seem to spot the error, could someone double check my work to see where I made a mistake?
The curl call that works:
curl --request POST https://connect.squareupsandbox.com/v2/payments \
--header "Content-Type: application/json" \
--header "Authorization: Bearer accesstoken112233" \
--header "Accept: application/json" \
--data '{
"idempotency_key": "ab2a118d-53e2-47c6-88e2-8c48cb09bf9b",
"amount_money": {
"amount": 100,
"currency": "USD"},
"source_id": "cnon:CBASEITjGLBON1y5od2lsdxSPxQ"}'
My Retrofit call:
public interface IMakePayment {
#Headers({
"Accept: application/json",
"Content-Type: application/json",
"Authorization: Bearer accesstoken112233"
})
#POST(".")
Call<Void> listRepos(#Body DataDto dataDto);
}
DataDto class:
public class DataDto {
private String idempotency_key;
private String amount_money;
private String source_id;
public DataDto(String idempotency_key, String amount_money, String source_id) {
this.idempotency_key = idempotency_key;
this.amount_money = amount_money;
this.source_id = source_id;
}
}
And lastly making the retrofit call:
DataDto dataDto = new DataDto("ab2a118d-53e2-47c6-88e2-8c48cb09bf9b", "{\"amount\": 100, \"currency\": \"USD\"}", "cnon:CBASEITjGLBON1y5od2lsdxSPxQ");
RetrofitInterfaces.IMakePayment service = RetrofitClientInstance.getRetrofitInstance().create(RetrofitInterfaces.IMakePayment.class);
Call<Void> call = service.listRepos(dataDto);
call.enqueue(new Callback<Void>() {
#Override
public void onResponse(#NonNull Call<Void> call, #NonNull Response<Void> response) {
Log.d(TAG, "onResponse: " + response.toString());
}
#Override
public void onFailure(#NonNull Call<Void> call, #NonNull Throwable t) {
Log.d(TAG, "onFailure: Error: " + t);
}
});
Retrofit Instance:
public class RetrofitClientInstance {
private static Retrofit retrofit;
private static final String BASE_URL = "https://connect.squareupsandbox.com/v2/payments/";
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
Edit 1: Changing to second parameter to JSON Object
JSONObject jsonObject = new JSONObject();
try{
jsonObject.put("amount", 100);
jsonObject.put("currency", "USD");
}catch (Exception e){
Log.d(TAG, "onCreate: " + e);
}
DataDto dataDto = new DataDto("ab2a118d-53e2-47c6-88e2-8c48cb09bf9b", jsonObject, "cnon:CBASEITjGLBON1y5od2lsdxSPxQ");
First of all, let's see what 400 means
The HyperText Transfer Protocol (HTTP) 400 Bad Request response status
code indicates that the server cannot or will not process the request
due to something that is perceived to be a client error (e.g.,
malformed request syntax, invalid request message framing, or
deceptive request routing).
Now we are sure, the problem stands in our request (not server fault), most probably it is because you are trying to convert JSON in request (do not do this explicitly GSON will convert automatically)
Use interceptor to verify your outgoing network requests (Tell the result here)
you use #POST(".") which does not make sense, please understand BASE_URL is your server URL NOT MORE
The problem could be translating this post request
So a possible solution
Change base URL into "https://connect.squareupsandbox.com/"
Replace #POST(".") with #POST("v2/payments/")
PS. #NaveenNiraula mentioned right thing even though it did not help you, please follow his instruction, it is the correct way parsing data using GSON (make sure you include it and configure it correctly) converter
EDIT
I make it work (I eliminated 400 error code that is what you want as long as question title is concerned) partially which means I detect why 400 error was occurred and fixed it but unfortunately, I stuck the UNAUTHORIZED issue. The problem was relating to converting json and data type
data class DataDTO(
val idempotency_key: String,
val source_id: String,
val amount_money: MoneyAmount
)
data class MoneyAmount(
val amount: Int,
val currency: String
)
I gist all code here you can refer
You need two DTO classes as below:
public class Amount_money
{
private String amount;
private String currency;
public String getAmount ()
{
return amount;
}
public void setAmount (String amount)
{
this.amount = amount;
}
public String getCurrency ()
{
return currency;
}
public void setCurrency (String currency)
{
this.currency = currency;
}
#Override
public String toString()
{
return "ClassPojo [amount = "+amount+", currency = "+currency+"]";
}
}
And
public class DataDto
{
private String idempotency_key;
private Amount_money amount_money;
private String source_id;
public String getIdempotency_key ()
{
return idempotency_key;
}
public void setIdempotency_key (String idempotency_key)
{
this.idempotency_key = idempotency_key;
}
public Amount_money getAmount_money ()
{
return amount_money;
}
public void setAmount_money (Amount_money amount_money)
{
this.amount_money = amount_money;
}
public String getSource_id ()
{
return source_id;
}
public void setSource_id (String source_id)
{
this.source_id = source_id;
}
#Override
public String toString()
{
return "ClassPojo [idempotency_key = "+idempotency_key+", amount_money = "+amount_money+", source_id = "+source_id+"]";
}
}
You need to create object for each like under :
Amount_money am = new Amount_money();
am.setAmount("100");
am.setCurrency("USD");
DataDto dto = new DataDto();
dto.setIdempotency_key("your key");
dto.setsource_id("your id");
dto.setAmount_money(am);
RetrofitInterfaces.IMakePayment service = RetrofitClientInstance.getRetrofitInstance().create(RetrofitInterfaces.IMakePayment.class);
Call<Void> call = service.listRepos(dataDto);
// yo get the point follow along
Most likely the passed JSON structure is not serialized in the same format.
"amount_money": {
"amount": 100,
"currency": "USD"},
I would at first use for private String amount_money; a real DTO having the amount and currency fields. This should give progress. I'm not 100% sure how the underscore mapping of attributes looks like, but this is the next step.
Add logging to be able to see the passed data. A quick search reveals this tutorial: https://futurestud.io/tutorials/retrofit-2-log-requests-and-responses. When seeing the transmitted data it should be easy to compare the expected and sent data.
Please check your base url.
In your curl you have https://connect.squareupsandbox.com/v2/payments
But in the code you have
private static final String BASE_URL = "https://connect.squareupsandbox.com/v2/payments/";
There is extra / (slash) in the end. I've seen cases where it was the issue. Could be your problem :)

How to receive the data sent from client

I am trying to send data (POST request) from android app using retrofit2 library and receive it on the server which is written in nodejs (using express framework) but i am not able to retrieve the data which is sent from the the app.
I used retrofit with GsonConverterfactory and sent a POST request to "/temp" route.
Index.js (handles route requests):-
var express = require("express");
var bodyParser = require("body-parser");
var app = express();
app.use(bodyParser.urlencoded({extended:true}));
app.use('/public',express.static('public'));
app.post("/temp",function(req,res){
console.log(req.body);
obj = {
orgName : "Got the message ",
address : "on the server"
}
res.json(obj);
})
app.listen(8000,function(){
console.log("Server Started at port 8000");
})
Shop.java
package com.example.myapplication;
public class Shop {
private String orgName;
private String address;
public Shop(String orgName, String address) {
this.orgName = orgName;
this.address = address;
}
public String getOrgName() {
return orgName;
}
public String getAddress() {
return address;
}
}
ShopApi.java
package com.example.myapplication;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
public interface ShopApi {
#POST("temp")
Call<Shop> create(#Body Shop shop);
}
postData() - Method to post data from MainActivity.java
public void postData(View view){
String BASE_URL = "http://10.0.2.2:8000/";
String org = orgName.getText().toString();
String address = add.getText().toString();
//Toast.makeText(getApplicationContext(),org+" "+address,Toast.LENGTH_SHORT).show();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
ShopApi shopApi = retrofit.create(ShopApi.class);
Shop shop = new Shop(org,address);
Call<Shop> shopCall = shopApi.create(shop);
shopCall.enqueue(new Callback<Shop>() {
#Override
public void onResponse(Call<Shop> call, Response<Shop> response) {
if(!response.isSuccessful()){
Toast.makeText(getApplicationContext(),response.code(),Toast.LENGTH_LONG).show();
}
Shop shopResponse = response.body();
String content = shopResponse.getOrgName() + shopResponse.getAddress();
Toast.makeText(getApplicationContext(),content,Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<Shop> call, Throwable t) {
Toast.makeText(getApplicationContext(),t.getMessage(),Toast.LENGTH_SHORT).show();
}
});
}
A json object is expected in the req.body which should be printed in terminal but this gets printed :-
Server Started at port 8000
{}
Please help me to retrieve data on sever.
I don't work in Node.js still I try to answer your question.
You're expecting a JSON object in server like below,
{
"orgName": "Organization name",
"address": "Organization address"
}
Okay, your Android part (Retrofit api interface) is correct. In run-time it produces the expected JSON object. But, in your server side you're accepting application/x-www-form-urlencoded instead of application/json data. Which is causing this issue IMO.
Just replace the following code from
app.use(bodyParser.urlencoded({extended:true}));
to
app.use(bodyParser.json());
And give it a try once!
body-parser doc

Creating Resource with references using spring data rest

I am using spring data rest, I have following entities exposed via spring data rest
DonationRequest
#Data
#Entity
#Table(name="donation_request",schema="public")
public class DonationRequest {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="donation_request_id")
Integer donationRequestId;
#Column(name="expiry_datetime")
Date expiryDatetime;
#Column(name="blood_group")
String bloodGroup;
#Column(name="no_of_bottles")
String noOfBottles;
#OneToOne
#JoinColumn(name="hospital_id")
Hospital hospital;
#OneToOne
#JoinColumn(name="user_data_id")
UserData requester;
#Column(name="active")
Boolean active;
}
Hospital
#Data
#Entity
#Table(name="hospital",schema="public")
public class Hospital {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="hospital_id")
Integer hospitalId;
#Column(name="name")
String name;
#Column(name="address")
String address;
#Column(name="loc",columnDefinition = "geometry")
Point loc;
}
Now I have an android client which has the same class definitions as stated above. Hospitals are cached at startup on android client. Now I want to create a donationRequest entity on server. I can do that easily by posting json of donationRequest object to /api/donationRequests. this json contains hospital object also. But the newly created donationRequest and hospital are not linked together.
Following type of json in postman does not create link:
{
"bloodGroup":"AB+",
"hospital":{
"hospitalId":1
}
}
I know that following json does create link:
{
"bloodGroup":"AB+",
"hospital":"/api/hospitals/1"
}
My question is how can I create link using first type of json as that is the natural way to serialize dontaionRequest object from android client? Also I want hospitals to be exposed via /api/hospitals, so removing that rest resource is not an option.
It can be achieved by using a custom HttpMessageConverter and defining a custom content-type which can be anything other than standard (I used application/mjson):
MHttpMessageConverter.java
public class MHttpMessageConverter implements HttpMessageConverter<Object>{
#Override
public boolean canRead(Class<?> aClass, MediaType mediaType) {
if (mediaType.getType().equalsIgnoreCase("application")
&& mediaType.getSubtype().equalsIgnoreCase("mjson"))
return true;
else
return false;
}
#Override
public boolean canWrite(Class<?> aClass, MediaType mediaType) {
return false;
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return new ArrayList<>(Arrays.asList(MediaType.APPLICATION_JSON));
}
#Override
public Object read(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
ObjectMapper mapper = new ObjectMapper();
Object obj = mapper.readValue(httpInputMessage.getBody(),aClass);
return obj;
}
#Override
public void write(Object o, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
}
}
CustomRestConfiguration.java
#Configuration
public class CustomRestConfiguration extends RepositoryRestConfigurerAdapter {
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new MHttpMessageConverter());
}
}
Spring Data REST is using HATEOAS. To refer to associated resources we have to use links to them:
Create a hospital first
POST /api/hospitals
{
//...
}
response
{
//...
"_links": [
"hostpital": "http://localhost/api/hospitals/1",
//...
]
}
Then get 'hospital' (or 'self') link and add it to the 'donationRequests' payload
POST /api/donationRequests
{
"bloodGroup":"AB+",
"hospital": "http://localhost/api/hospitals/1"
}
Another approach - create first 'donationRequests' without hospital
POST /api/donationRequests
{
//...
}
response
{
//...
"_links": [
"hostpital": "http://localhost/api/donationRequests/1/hospital"
//...
]
}
then PUT hospital to donationRequests/1/hospital using text link to hospital in your payload (pay attention to Content-Type: text/uri-list)
PUT http://localhost/api/donationRequests/1/hospital (Content-Type: text/uri-list)
http://localhost/api/hospitals/1
Info: Repository resources - The association resource
UPDATE
If it's necessary to deal without links to resources we have to make a custom rest controller.

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.

Retrofit returns object with null members

When I try to parse the following JSON with Retrofit, I end up with null member objects.
Parsing:
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(CallerInfo.API_URL)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
InGameInfo igi = restAdapter.create(InGameInfo.class);
Game game = igi.fetchInGameInfo("EUW", "sasquatching");
Log.d("Cancantest", "Game " + game); //Not null
Log.d("Cancantest", "Team one " + game.getTeamOne()); //Null
Game Class:
#SerializedName("teamTwo")
#Expose private Team teamTwo;
#SerializedName("teamOne")
#Expose private Team teamOne;
public void setTeamOne(Team teamOne) {
this.teamOne = teamOne;
}
public void setTeamTwo(Team teamTwo) {
this.teamTwo = teamTwo;
}
public Team getTeamOne() {
return teamOne;
}
public Team getTeamTwo() {
return teamTwo;
}
Team Class:
#SerializedName("array")
#Expose private TeamMember[] teamMembers;
public void setTeamMembers(TeamMember[] teamMembers) {
this.teamMembers = teamMembers;
}
public TeamMember[] getTeamMembers() {
return teamMembers;
}
Example JSON:
{
"game":{
"teamTwo":{
"array":[]
},
"teamOne":{
"array":[]
}
}
}
The JSON contains a top level "game" entry so you cannot directly deserialize an instance of game. You need another type which has a field of type Game that represents the response.
public class Response {
public final Game game;
public Response(Game game) {
this.game = game;
}
}
You can put your JSON in a string and use Gson directly to test how the response will be deserialized. This behavior has almost nothing to do with Retrofit and all to do with the behavior of Gson.
String data = "...";
Game game = gson.fromJson(data, Game.class);
Response response = gson.fromJson(data, Response.class);
There can be one more reason for somewhat similar behavior: in this case debugger actually has no field members for the response returned from Retrofit.
And the reason for that is proguard. If you are using minifyEnabled true, make sure you explicitly tell it to keep your POJOs. It can be something like that:
#save model classes
-keep class com.example.app.**.model.** {*; }

Categories

Resources