I am looking for an example how could I send file and other params together to server.
I have to send server JSON which
{
"title": "title",
"file": "uploaded file instance",
"location": {
"lat": 48.8583,
"lng": 2.29232,
"place": "Eiffel Tower"
}
}
How could I create Retrofit to handle this case?
If file is a string I know how to handle this. If file is File object I have no idea how to do this.
Use gson and create a model class for the location.
Add the following dependencies to your build.gradle.
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile 'com.google.code.gson:gson:2.5'
Create a model to represent the location.
public class Location {
double lat;
double lng;
String location;
public Location(double lat, double lon, String place) {
this.lat = lat;
this.lon = long;
this.place = place;
}
}
If the variable names for the payload fields don't match the actual required name for the endpoint you will need to add the annotation #SerializedName([expected name])
ex:
import com.google.gson.annotations.SerializedName;
public class Location {
#SerializedName("lat")
double latitude;
#SerializedName("lng")
double longitude;
#SerializedName("place")
String location;
public Location(double lat, double lon, String place) {
latitude = lat;
longitude = long;
location = place;
}
}
Define the api interface.
public interface Api {
#POST("upload/")
#Multipart
Call<ResponseBody> uploadFile(#Part("title") RequestBody title,
#Part MultipartBody.Part imageFile,
#Part("location") Location location
);
}
Create a Retrofit instance and call the api.
File file;
// create retrofit instance
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://baseurl.com/api/")
.addConverterFactory(GsonConverterFactory.create())
.build();
// create api instance
Api api = retrofit.create(Api.class);
// create call object
Call<ResponseBody> uploadFileCall = api.uploadFile(
RequestBody.create(MediaType.parse("text/plain"), "title"),
MultipartBody.Part.createFormData(
"file",
file.getName(),
RequestBody.create(MediaType.parse("image"), file)),
new Location(48.8583, 2.29232, "Eiffel Tower"));
// sync call
try {
ResponseBody responseBody = uploadFileCall.execute().body();
} catch (IOException e) {
e.printStackTrace();
}
// async call
uploadFileCall.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
// TODO
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
// TODO
}
});
You will need to change the MediaType.parse() call if you are not using an image file.
You can similarly create a custom response type object and replace ResponseBody with it to receive a deserialized result object.
Let me know if this works. I didn't have a chance to test in your exact scenario obviously but I'm fairly confident this should work. The only part I'm not 100% on is whether #Part("location") Location location should be #Body("location") Location location
As of 02.2020 #Abtin Gramian's answer is great, except RequestBody.create() and MediaType.parse() are deprecated in Kotlin, so you have to use:
file.asRequestBody("image".toMediaTypeOrNull())
instead of:
RequestBody.create(MediaType.parse("image"), file)
Also, I had to write the following manually:
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
Related
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 :)
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
I'm writing a lambda expression to convert the given latitude and longitude into an address. The expression is supposed to take co-ordinates as arguments and returns their corresponding address. However, the value returned is null. Following is my class:
public class LambdaDeclarations {
String loc;
private static final String TAG = "LambdaDeclarations";
public CoordinatesToAddressInterface convert = (latitude, longitude, context) -> {
RequestQueue queue = Volley.newRequestQueue(context);
Log.d(TAG, "onCreate: Requesting: Lat: "+latitude+" Lon: "+longitude);
String url ="https://maps.googleapis.com/maps/api/distancematrix/json?units=metric&origins="+latitude+","+longitude+"&destinations="+latitude+","+longitude+"&key=AIzaSyCdKSW0glin4h9sGYa_3hj0L83zI0NsNRo";
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
(String response) -> {
try {
JSONObject jsonObject = new JSONObject(response);
JSONArray destinations = jsonObject.getJSONArray("destination_addresses");
Log.d(TAG, "GETRequest: JSON Object: "+destinations.toString());
String location = destinations.toString();
Log.d(TAG, "Location: "+location);
setLocation(location);
} catch (JSONException e) {
e.printStackTrace();
}
}, error -> Log.d(TAG, "onErrorResponse: That didn't work!"));
queue.add(stringRequest);
return getLocation();
};
public String getLocation() {
return loc;
}
public void setLocation(String location) {
this.loc = location;
}
}
Following is the output from logcat:
09-16 10:31:09.160 26525-26525/com.rmit.tejas.mad_foodtruck_2 D/LambdaDeclarations: GETRequest: JSON Object: ["77 State Route 32, West Melbourne VIC 3003, Australia"]
Location: ["77 State Route 32, West Melbourne VIC 3003, Australia"]
09-16 10:31:09.176 26525-26525/com.rmit.tejas.mad_foodtruck_2 D/LambdaDeclarations: GETRequest: JSON Object: ["111 Adderley St, West Melbourne VIC 3003, Australia"]
Location: ["111 Adderley St, West Melbourne VIC 3003, Australia"]
09-16 10:31:09.177 26525-26525/com.rmit.tejas.mad_foodtruck_2 D/LambdaDeclarations: GETRequest: JSON Object: ["4\/326 William St, Melbourne VIC 3000, Australia"]
Location: ["4\/326 William St, Melbourne VIC 3000, Australia"]
Following is my usage:
myViewHolder.textView3.setText("Location: i->"+i+" add: "+l.convert.toAddress(trackingInfos.get(i).getLatitude(),trackingInfos.get(i).getLongitude(),context));
l is an object of the class LambdaDeclarations and following is the relevant interface:
public interface CoordinatesToAddressInterface {
String toAddress(double latitude, double longitude, Context context);
}
When I try to print the coordinates from the relevant adapter they are getting printed correctly. So the location is getting set properly but when I try to access it from another class it shows me a null value for the string. Can you please advise an alternate method to extract the location from the expression?
First of all, Lambda Expression is just a anonymous class implementation, it was design to be used as a method or class argument and solve shadowing issues of anonymous class.
So in your case, you don't need it at all, just simply implement CoordinatesToAddressInterface interface as named class as usual.
Second, you used Volley wrong, the first lambda you provided to StringRequest, hereafter will be call response callback, is going to be called when HTTP request finish but the return statement
return getLocation();
will return null immediately before your setLocation(location) or even your response callback ever get executed, that why you got null every time you call convert(), though you can still see log that you print because the response callback will be executed anyway (assume that request is success).
To use response callback correctly, you have to update your UI inside callback, pretty much like this
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
public static final String TAG = "MyAdapter";
private RequestQueue mQueue;
public MyAdapter(Context context) {
this.mQueue = Volley.newRequestQueue(context);
}
public RequestQueue getMyAdapterRequestQueue() {
return this.mQueue;
}
...
#Override
public void onBindViewHolder(#NonNull final MyViewHolder holder, int position) {
String url ="some url";
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
(String response) -> {
try {
JSONObject jsonObject = new JSONObject(response);
JSONArray destinations = jsonObject.getJSONArray("destination_addresses");
Log.d(TAG, "GETRequest: JSON Object: "+destinations.toString());
String location = destinations.toString();
Log.d(TAG, "Location: "+location);
// update UI
holder.mTextView.setText(location);
} catch (JSONException e) {
e.printStackTrace();
}
}, error -> Log.d(TAG, "onErrorResponse: That didn't work!"));
stringRequest.setTag(TAG);
mQueue.add(stringRequest);
}
Of course, you can edit your interface's method signature and make your adapter implement that interface (I would rather do it this way though) but the point is that you have to process asynchronous results in callback method, never expect asynchronous operation's callback to finish before your next lines of code.
RequestQueue shouldn't be created per-request since it manages internal state that help you make request faster (caching), you can also cancel requests too in an event like phone rotation and your will get destroy, in this case, just call cancel method in Activity/Fragment's onStop()
#Override
protected void onStop () {
super.onStop();
if (myAdapter.getMyAdapterRequestQueue() != null) {
myAdapter.getMyAdapterRequestQueue().cancelAll(MyAdapter.TAG);
}
}
Response callback won't be called after you cancel request.
i'm using for firts time Retrofit on my Android app.
This is the structure of json object that i have to retrive:
{
"placemarks":[
{
"address":"Via di Santa Maria a Marignolle, 15, 50124 Firenze",
"coordinates":[
11.23348,
43.75855,
0
],
"engineType":"CE",
"exterior":"GOOD",
"fuel":100,
"interior":"GOOD",
"name":"049/EV284TP",
"smartPhoneRequired":false,
"vin":"WME4513341K774636"
}
]
}
i have write this Pojo model for object "placemarks" and all otehr items.
And i have write this code to retrive the json data and put it into map:
private void getPlacemark(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://www.car2go.com/api/v2.1/vehicles?loc=roma&oauth_consumer_key=roadzapp&format=json")
.addConverterFactory(GsonConverterFactory.create())
.build();
APIService service = retrofit.create(APIService.class);
Call<ResponsePlacemarks> call = service.getPlacemark();
call.enqueue(new Callback<ResponsePlacemarks>() {
#Override
public void onResponse(Response<ResponsePlacemarks> response, Retrofit retrofit) {
Log.d("response: ", String.valueOf(response.body()));
try {
mMap.clear();
// This loop will go through all the results and add marker on each location.
for (int i = 0; i < response.body().getPlacemarks().size(); i++) {
Double lat = response.body().getPlacemarks().get(i).getCoordinates().get(1);
Double lng = response.body().getPlacemarks().get(i).getCoordinates().get(0);
String placeName = response.body().getPlacemarks().get(i).getAddress();
MarkerOptions markerOptions = new MarkerOptions();
LatLng latLng = new LatLng(lat, lng);
// Position of Marker on Map
markerOptions.position(latLng);
// Adding Title to the Marker
markerOptions.title(placeName);
// Adding Marker to the Camera.
Marker m = mMap.addMarker(markerOptions);
// Adding colour to the marker
markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED));
// move map camera
mMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));
mMap.animateCamera(CameraUpdateFactory.zoomTo(11));
}
} catch (Exception e) {
Log.d("onResponse", "There is an error");
e.printStackTrace();
}
}
#Override
public void onFailure(Throwable t) {
}
});
}
but now when i run app the json are note load and i have error in the first line of for loop:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List android.mobility.com.mobiity.model.ResponsePlacemarks.getPlacemarks()' on a null object reference
Hi have found the error.
On APIService i have set #GET method in this way: #GET(".") because i use as URL the URL into BaseURL. So if i set all url into #GET the method works fine.
How i can use only the url into BaseULRalso into #GET?
First, you can only call response.body() exactly once.
So, comment this.
// Log.d("response: ", String.valueOf(response.body()));
And extract that list variable
final ResponsePlacemarks _response = response.body();
final List<Placemark> placemarks = _response.getPlacemarks();
for (int i = 0; i < placemarks.size(); i++) {
final Placemark p = placemarks.get(i);
Coordinates c = p.getCoordinates();
Double lat = c.get(1);
Double lng = c.get(0);
String placeName = p.getAddress();
And if that doesn't work, then you need for the Java object to exactly match the JSON response, otherwise it is null
How i can use only the url into BaseULRalso into #GET?
Your base URL should look like something this
https://www.car2go.com/api/v2.1
Then you should be able to have something like
#GET("/vehicles")
public ResponsePlacemarks getVehicles(
#Query("oauth_consumer_key") String key,
#Query("format") String format
#Query("loc") String loc
);
public ResponsePlacemarks getVehicles(String loc) {
return getVehicles("roadzapp", "json", loc);
}
Or maybe just
#GET("/vehicles?format=json")
public ResponsePlacemarks getVehicles(
#Query("oauth_consumer_key") String key,
#Query("loc") String loc
);
The reason for putting the key into the method call is that you shouldn't store the key as a string on your device for security reasons.
Most likely your POJO does not match the JSON structure.
Can you post your POJO class and how are you deserializing the JSON?
I'm trying to get a json list from a web service.
This is the json string return by server :
[{"categoryName":"Política"},{"categoryName":"Economía"},{"categoryName":"Cultura"},{"categoryName":"Deportes"}
The problem is converting in to the POJO. The special characters (í) it's appear like "Pol�tica".
This is the retrofit call function :
#GET("categories")
public Call<List<CategoryPojo>> getCategorias(#Query("sitename") String site)
this is the callback function:
Call<List<CategoryPojo>> call = restservice.getApiService().getCategorias(medio);
try {
call.enqueue(new Callback<List<CategoryPojo>>() {
#Override
public void onResponse(Call<List<CategoryPojo>> call, Response<List<CategoryPojo>> response) {
List<CategoryPojo> categories = response.body();
if (listener != null)
listener.onDataLoaded(categories);
}
#Override
public void onFailure(Call<List<CategoryPojo>> call, Throwable throwable) {
Log.e("Retrofit Error", throwable.getMessage());
}
});
this is the POJO:
public class CategoryPojo implements Serializable{
public CategoryPojo() { }
#SerializedName("categoryName")
private String name;
public String getName()
{
return this.name;
}
}
The result of the request to the Web services, (output in browser) is :
[{"categoryName":"Política"},{"categoryName":"Economía"},{"categoryName":"Cultura"},{"categoryName":"Deportes"},{"categoryName":"Salud"},{"categoryName":"Ciencia y Tecnología"},{"categoryName":"Medio Ambiente"},{"categoryName":"Medios"},{"categoryName":"Militar e Inteligencia"},{"categoryName":"Sociedad"}]
So, the return json has a good encoding...i think that maybe is about the way retrofit read the response.
I'm using retrofit-2.0.2, gson-2.6.1, converter-gson-2.0.2, okhttp-3.2.0.
Any help? please
You should check Content-type in the response headers. Look for the charset value and try to change that on the backend side to application/josn;charset=UTF-8. That worked for me.