How to properly manage a Retrofit POST request? - android

Actually i'm trying to post a text file to my server and i would be able to handle the error when i've sent the file but i didn't get the response (In case the file has been sent the network is gone down so the server received the file but the device don't know it)
Because for now i'm getting an issue because if i don't get the response when the device return online it send again the same file and i would prevent it.
Seems it's hidely retry to send the file on connection fail or something like that.
Here is my method which i use onClick to send the file:
public void sendPost() {
#SuppressLint({"SdCardPath", "DefaultLocale"}) final File file = new File("/data/data/com.example.igardini.visualposmobile/files/"+String.format("%03d", Integer.valueOf(nTerminalino))+"_"+nTavoli.getText().toString()+".txt");
final MultipartBody.Builder builder = new MultipartBody.Builder();
builder.setType(MultipartBody.FORM);
final RequestBody fbody = RequestBody.create(MediaType.parse("text/*"), file);
builder.addFormDataPart(String.format("%03d", Integer.valueOf(nTerminalino))+"_"+nTavoli.getText().toString(), file.getName(),fbody);
final MultipartBody requestBody = builder.build();
final APIService apiService = ApiUtils.getAPIService(ipCASSA);
final Call<Void> calls = apiService.savePost(requestBody);
calls.enqueue(new Callback<Void>() {
#Override
public void onResponse(#NonNull Call<Void> call, #NonNull Response<Void> response) {
if (response.isSuccessful()) {
Log.i("RESPONSE: ", response.toString());
Print();
dialogLoading.dismiss();
Runtime.getRuntime().gc();
// checkFile task = new checkFile();
// task.execute();
}else {
Toast.makeText(pterm.this,"ERRORE",Toast.LENGTH_LONG).show();
}
}
#Override
public void onFailure(#NonNull Call<Void> call, #NonNull Throwable t) {
if(t instanceof IOException) {
Log.e("TAG", t.toString());
MediaPlayer mpFound = MediaPlayer.create(pterm.this, R.raw.errorsound);
mpFound.start();
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
if (v.hasVibrator()) {
v.vibrate(6000);
}
new GlideToast.makeToast(pterm.this, "CONNESSIONE FALLITA!", GlideToast.LENGTHLONG, GlideToast.FAILTOAST).show();
dialogLoading.dismiss();
}
}
});
}
While here are my other useful classes:
class ApiUtils {
private ApiUtils() {}
static APIService getAPIService(String ipCASSA) {
String BASE_URL = "http://"+ipCASSA+"/web/";
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10000, TimeUnit.MILLISECONDS)
.writeTimeout(10000, TimeUnit.MILLISECONDS)
.readTimeout(10000, TimeUnit.MILLISECONDS)
.retryOnConnectionFailure(false)
.build();
return RetrofitClient.getClient(BASE_URL,okHttpClient).create(APIService.class);
}
}
public interface APIService {
#POST("CART=PTERM")
Call<Void> savePost(#Body RequestBody text);
}
class RetrofitClient {
private static Retrofit retrofit = null;
static Retrofit getClient(String baseUrl, OkHttpClient okHttpClient) {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}

In that case I would add to file some sort of device generated random ID, and send it along with request.
On server side I would change logic to first try to locate file in DB using this ID, if there is none, store it and return status OK along with maybe server side id (might come handy later).
If there is file with ID provided on request, just return server side ID with status 200 - that way you will not store it twice.
You can even go further - if you have not stable connection (it seems like it from description) you could even make seperate REST call to check if file is on server side by generated id or checksum, and if not then start to send it - it will safe bandwitch.
It does not have to be generated ID - it can be checksum of a file for example. Whatever is easier for you.
For generating ID you could use Android's UUID
https://developer.android.com/reference/java/util/UUID
For checksum check this answer:
How to generate an MD5 checksum for a file in Android?
BTW - I would not do 10 seconds timeout limit - it is way too much, and user experience will be terrible

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 :)

Android Retrofit with redirection

I am developing simple Android app where I am using google spreadsheet as a data source. For communication I am using google app script which implements doPost method because my app is sending some data to sheet and also wants some data as a response. The problem is instead of json response I always get html response about redirection in the errorBody().
I have also set OkHttpClient with redirections enabled to my retrofit service, but result is still the same.
I am working with Insomnia rest client for debugging and when I set redirections on there, everything works there fine.
If somebody had the same problem and solved it, please help.
Edit:
Here is my code:
public class Connector {
private static final String BASE_URL = "https://script.googleusercontent.com/";
private static final Object LOCK = new Object();
private static CallTaxiService service;
private static final String TAG = "Connector";
private static CallTaxiService getService()
{
if (service == null)
{
synchronized(LOCK) {
Log.d(TAG, "creating instance");
service = buildService();
}
}
return service;
}
private static CallTaxiService buildService()
{
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(new OkHttpClient.Builder().followRedirects(true)
.followSslRedirects(true).build())
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit.create(CallTaxiService.class);
}
public static void syncData(List<TaxiServiceAppData> data, Callback<Response> callback)
{
Call<Response> call = getService().sendData(data);
Log.d(TAG, "syncing data");
call.enqueue(callback);
}
private interface CallTaxiService {
#Headers({"Content-type: application/json"})
#POST("endpoint_url")
Call<Response> sendData(#Body List<TaxiServiceAppData> data);
}
}
And here is how I am calling it:
Connector.syncData(taxiServiceAppData, new retrofit2.Callback<com.adrisoft.calltaxi.model.Response>() {
#Override
public void onResponse(Call<com.adrisoft.calltaxi.model.Response> call, Response<com.adrisoft.calltaxi.model.Response> response) {
com.adrisoft.calltaxi.model.Response data = response.body();
if (data != null) {
newCities = data.getCities();
newTaxis = data.getTaxis();
updateDb();
prefs.saveSyncTime();
isSyncRunning = false;
callback.onSuccess();
} else {
try {
Log.d(TAG, "Sync failed ... no data available. Error: " + response.errorBody().string());
} catch (Exception ex) {
}
callback.onFailure();
}
}
#Override
public void onFailure(Call<com.adrisoft.calltaxi.model.Response> call, Throwable t) {
Log.d(TAG, "Sync request failed.");
isSyncRunning = false;
callback.onFailure();
}
});
And exactly in the log "Sync failed ... no data available ..." I am getting this from errorBody():
<HTML>
<HEAD>
<TITLE>Temporary Redirect</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000">
<H1>Temporary Redirect</H1>
The document has moved here.
</BODY>
</HTML>
Redirect could have happened because the server endpoint provided https and in your code you call http. Then the server would redirect to https. Only GET requests can be redirected, so others like POST will result in error.

How to get HTTP request/response timings in android

I need to log these:
DNS time
Connection time
SSL time
Device network Bandwidth
First byte time
Transfer time
No of objects/No of bytes
I am using OKHttp library for network requests.
Take a look at OkHttp's new EventListener: https://github.com/square/okhttp/wiki/Events
It provides a way to hook-up listener to every step of the hcian, so you can get info like so:
class PrintingEventListener extends EventListener {
private long callStartNanos;
private void printEvent(String name) {
long nowNanos = System.nanoTime();
if (name.equals("callStart")) {
callStartNanos = nowNanos;
}
long elapsedNanos = nowNanos - callStartNanos;
System.out.printf("%.3f %s%n", elapsedNanos / 1000000000d, name);
}
#Override public void callStart(Call call) {
printEvent("callStart");
}
#Override public void callEnd(Call call) {
printEvent("callEnd");
}
#Override public void dnsStart(Call call, String domainName) {
printEvent("dnsStart");
}
#Override public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
printEvent("dnsEnd");
}
...
}
And you hook it up like this:
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
System.out.println("REQUEST 1 (new connection)");
try (Response response = client.newCall(request).execute()) {
// Consume and discard the response body.
response.body().source().readByteString();
}
Which will output the following:
REQUEST 1 (new connection)
0.000 callStart
0.010 dnsStart
0.017 dnsEnd
0.025 connectStart
0.117 secureConnectStart
0.586 secureConnectEnd
0.586 connectEnd
0.587 connectionAcquired
0.588 requestHeadersStart
0.590 requestHeadersEnd
0.591 responseHeadersStart
0.675 responseHeadersEnd
0.676 responseBodyStart
0.679 responseBodyEnd
0.679 connectionReleased
0.680 callEnd
AS you have mention you are using OKHttp library for HTTP calling, OKHttp library provide facility to print logs of every single API call that you need using logging-interceptor dependency.
You can have more details in below link and follow the steps of below link.
https://www.learn2crack.com/2016/06/retrofit-okhttp-logging-interceptor.html
Add HttpLoggingInterceptor.It logs the total request time. It also will log the Ok-Http-Sent and Ok-Http-Received headers.
public static Retrofit getInstance() {
if (instance == null) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(logging); // <-- this is the important line!
instance = new Retrofit.Builder().baseUrl(Constant.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
}
return instance;
}
And add the following dependency:
compile "com.squareup.okhttp3:logging-interceptor:3.3.1"

JSON Null Poiner Exception

I got such a very strange problem. I already created weather application which using this API. Problem is that, when i am calling "CONDITION" object,than i am getting a null pointer exception.But when i am calling another object,than everything is fine! Its weird. During 2 weeks this API worked fine,but starting from yesterday, not. Can anyone explain me,why it is like that, and what i have to do. Because application is already finished, and i am making the another one, and i don't want to rewriting this application just because of that problem.
I put my response in another application, to check maybe the problem was in my application,not in the API. Here is the code..
API CLASS:
public interface WeatherAPI {
#GET("/v1/current.json?key=fe1c9cd3fd2d4b189c010010172505")
Call<Weather> getForecastWeather(
#Query("q") String cityName
// #Query("days")Integer days
);
}
CONTROLLER CLASS:
public class Controller {
static final String BASE_WEATHER_URL = "https://api.apixu.com/";
public static WeatherAPI getApi(){
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_WEATHER_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
WeatherAPI weatherAPI = retrofit.create(WeatherAPI.class);
return weatherAPI;
}
}
MAIN ACTIVITY CLASS:
mWeatherAPI = Controller.getApi();
mWeatherAPI.getForecastWeather("Paris").enqueue(new Callback<Weather>() {
#Override
public void onResponse(Call<Weather> call, Response<Weather> response) {
String description = response.body().getCurrent().getCondition().getIcon();
Log.e("TAG", "description: " + description);
}
#Override
public void onFailure(Call<Weather> call, Throwable t) {
Log.e("TAG", "onFailure: " + t.getMessage());
}
});
Yes, you need to add OkHttpClient to your Retrofit:
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
Retrofit retrofit = Retrofit.Builder().baseUrl(yourBaseUrlString)
.addConverterFactory(GsonConverterFactory.create())
.retrofitBuilder.client(okHttpClientBuilder.build()).build();
Check the json Object whether u created or not.

Retrofit 2.0, request GET to a .json file as endpoint

Hello Im working in a test with Retrofit 2.0 and one of the test is making a resquest to a url that finish with .json:
Example: https://domain.com/contacts.json
baseURl: https://domain.com/
endPoint: /contacts.json
Which is a file, but I want to make a normal GET request and get the json inside directly
If you have control over your web server, you can customize it supports .json file as text/plain or application/json. Please see my following screenshot (I have done with IIS 7.5)
The following screenshot is a request using PostMan:
build.gradle file:
dependencies {
...
compile 'com.squareup.retrofit2:retrofit:2.0.1'
compile 'com.squareup.retrofit2:converter-gson:2.0.1'
}
WebAPIService.java:
public interface WebAPIService {
#GET("/files/jsonsample.json")
Call<JsonObject> readJson();
}
MainAcitivty.java:
Retrofit retrofit1 = new Retrofit.Builder()
.baseUrl("http://...")
.addConverterFactory(GsonConverterFactory.create())
.build();
WebAPIService service1 = retrofit1.create(WebAPIService.class);
Call<JsonObject> jsonCall = service1.readJson();
jsonCall.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
Log.i(LOG_TAG, response.body().toString());
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
Log.e(LOG_TAG, t.toString());
}
});
Logcat:
04-15 15:31:31.943 5810-5810/com.example.asyncretrofit I/AsyncRetrofit: {"glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossList":{"GlossEntry":{"ID":"SGML","SortAs":"SGML","GlossTerm":"Standard Generalized Markup Language","Acronym":"SGML","Abbrev":"ISO 8879:1986","GlossDef":{"para":"A meta-markup language, used to create markup languages such as DocBook.","GlossSeeAlso":["GML","XML"]},"GlossSee":"markup"}}}}}
Hello I found a solution to get the file using your code and it really works now but I haven't touch the MIME on the web server, I think I didn't have added the Converter in the code I guess. Thank you.
WebAPIService.java:
public interface WebAPIService {
#GET("/contacts.json")
Call<JsonObject> getContacts();
}
MainAcitivty.java:
Retrofit retrofit1 = new Retrofit.Builder()
.baseUrl(BuildConfig.API_ENDPOINT)
.addConverterFactory(GsonConverterFactory.create())
.build();
WebAPIService service1 = retrofit1.create(WebAPIService.class);
Call<List<Contact>> jsonCall = service1.getContacts();
jsonCall.enqueue(new Callback<List<Contact>() {
#Override
public void onResponse(Call<List<Contact>> call, Response<List<Contact>> response) {
Log.i(LOG_TAG, response.body().toString());
}
#Override
public void onFailure(Call<List<Contact>> call, Throwable t) {
Log.e(LOG_TAG, t.toString());
}
});

Categories

Resources