Error while trying to get data from a webservice with retrofit - android

I have an app that sometimes get crushed, it has never happened to me, some users have reported it... this is the stack trace that google sends me:
java.lang.NullPointerException:
at com.mal.saul.preciosbitcoinmexico.Fragment.FragmentPrincipal.
realizarCambioPrecio(FragmentPrincipal.java:181)
at com.mal.saul.preciosbitcoinmexico.Fragment.FragmentPrincipal.
access$200(FragmentPrincipal.java:42)
at com.mal.saul.preciosbitcoinmexico.Fragment.FragmentPrincipal$2.
onResponse(FragmentPrincipal.java:160)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.
run(ExecutorCallAdapterFactory.java:68)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5637)
at java.lang.reflect.Method.invoke(Native Method:0)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.
run(ZygoteInit.java:959)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:754)
i think my error is on the onResponse Method, here it is:
#Override
public void onResponse(Call<BtcValuesResponse> call,
Response<BtcValuesResponse> response) {
BtcValuesResponse btcValuesResponse =
response.body();
if(btcValuesResponse != null){
btcValues =
btcValuesResponse.getBtcValues();
showBtcValues();
}
else {
Toast.makeText(getActivity(), "Bitso No Está Disponible Por
el Momento", Toast.LENGTH_SHORT).show();
}
}
any idea?

You are requesting data by Retrofit asynchronously, after request is completed, you show data by calling showBtcValues().
In a case that your request has not completed yet but users change to other screen. So, when it finish, it still calls showBtcValues, then you will get crash because there is no UI component there.
Please try that to recreate the crash. I think it is the issue here.
If it is, the solution is that you should check the state of Fragment (or Activity) where you call request.

As stated before maybe your there is no UI component there
or
You are just assuming the the response Response<BtcValuesResponse> response is valid, but you should check if the API response is not rate limited, or due connectivity errors, the response was an invalid nonce or something else. You need to verify response status before unwrapping it.
if(response.isSuccessful()){
// Get the body, where the BTCValueResponse is
BtcValuesResponse responseBody = response.body();
btcValuesResponse.getBtcValues();
showBtcValues();
}else{
// Check the error stream
System.out.println(Utils.inputStreamToString(wrappedServiceResponse.errorBody().byteStream()));
}

Related

Firebase storage 'getBytes' doesn't have a cancel method

I am downloading a json file from storage using 'getBytes', but I want to cancel the download if it took more than 10 seconds. the method 'getBytes' returns a a 'Task' type which doesn't have a cancel method, unlike 'getFile' which returns 'FileDownloadTask' that indeed has a cancel method.
So, Is there a way to cancel the download using 'getBytes', Can I cast 'Task' to 'FileDownloadTask' ?
Edit: Here is my code :
mainStorageRef.child(UNIVERSITIES_DATA_STORAGE_PATH).getBytes(Long.MAX_VALUE).addOnSuccessListener(new OnSuccessListener<byte[]>() {
#Override
public void onSuccess(byte[] bytes) {
String jsonData = new String(bytes, StandardCharsets.UTF_8);
...
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
FastUtils.toastAndLogConnectionFailure(context, e);
}
});
firebaser here
As far as I can see from the (closed source) implementation, the task that you get back from getBytes() is not a FileDownloadTask. It is instead a direct Task<byte[]> that is constructed by a TaskCompletionSource object. For that reason, it can't be cancelled.
Your request seems reasonable though, so I'd recommend filing a feature request to make the engineering team aware of it.
For the moment, I'd recommend using the getStream() method, which gives you a StreamDownloadTask task that can be cancelled. You can then read the stream contents into a ByteArrayOutputStream, which is pretty much what the getBytes method does internally.

Android Retrofit - 404 errors on working URLs

I have an Android app that has this particular retrofit code that has not been touched in years. All of a sudden we started seeing 404 errors when making certain GET calls. When debugging through it, looking at the response, it looks like this:
Response{protocol=h2, code=404, message=, url=https://www.mainstreetartsfest.org/feed/artist/getAllCategories}
When I copy that URL into a browser, it loads the data fine. We are doing something similar on iOS with no issues, but on Android, it's failing now. I am completely stumped as to any reasons why this would have started occurring. Any ideas? The interface for the Retrofit API looks like this:
#GET("feed/artist/getAllCategories")
Call<List<Category>> getAllCategories();
Our base URL is defined as follows:
public static final String BASE_URL = "http://www.mainstreetartsfest.org/";
Let me know if more info is needed.
I have try this api on postman and this api is giving 404 Not Found. my backhand team says that there is some mistake in backhand side .
Something alike this should work - and won't break once they've fixed the problem on their side:
Call<Categories> api = SomeClient.getAllCategories();
api.enqueue(new Callback<Categories>() {
#Override
public void onResponse(#NonNull Call<Categories> call, #NonNull Response<Categories> response) {
switch(response.code()) {
/* temporary workaround: treating HTTP404 as if it would be HTTP200. */
case 200:
Log.e(LOG_TAG, "a proper HTTP200 header had been received.");
case 404:
if (response.body() != null) {
Categories items = response.body();
...
} else if (response.errorBody() != null) {
String errors = response.errorBody().string();
...
}
break;
}
}
}
Just logging an error on HTTP200, so that one can see when it's about time to fix the code.

Handle null server response cases

I'm calling an endpoint to GET a list of objects from the server. Using Retrofit2.0 + RxJava for the api calls.
The server has made it in such a way that when there are no items in the list, instead of returning an empty list, it returns null with the response code 204...
If I use an Observable<List<Item>> when the list is empty it will hit onError because of the null body
If I use Completableor Observable<Void> I won't be able to handle any data returned....
I thought about handling this by verifying the error message and assuming that if is an null exception to continue with the normal flow but I'm not fully comfortable with this hack....
Is there a way to handle this situation?
204 : No content means that the query successfully processed but no information to return.
You can use this in OnError method to have more visibilty :
#Override
public void onError(Throwable e) {
try {
Log.e("errorL", ((HttpException) e).response().errorBody().string());
} catch (IOException e1) {
e1.printStackTrace();}}
Good luck !

Conditional subsequent request with RxJava

I have an issue with my network client design. I have a use case, when the client tries to request an item from a REST API, but in case the API returns a 404 HTTP status code I need to send a request to create the item on the server and then request the item again.
I would like to use RxJava to avoid the callback hell. Is this a valid use case RxJava? Is it possible to create such a conditional sub-request?
Thank you for your time and answers.
Based on your question, I assume you have something that look like
public Observable<Item> getItem();
that will either return the item, or fire an error and
public Observable<?> createItem();
That will create one.
You can use those two together like so:
public Observable<Item> getOrCreateItem() {
return getItem().onErrorResumeNext(error -> {
// Depending on your framework, figure out which is the result code
if (error.getResultCode() == 404) {
return createItem().flatMap(ignored -> getItem());
} else {
return Observable.error(error);
}
});
}
With Retrofit, you'd have to simply make sure the exception is a RetrofitError, cast it, and get the response and the status code. (((RetrofitError) error).getResponse().getStatus())

Unexpected HTTP 400 status code from NanoHTTPD on Android

Friends!
I'm getting occasional and unexpected HTTP 400 responses from nanohttpd in my Android app. The error is following a specific pattern. I've been looking at this for some time now but I've come to the point where I need a different angle or some other help pointing me in the right direction.
Could you please have a look and share your thoughts or even direct points and suggestions?
Why am I getting this HTTP 400 status code?
And why only under the given circumstances? (I don't want it at all!)
Some Background
I'm running nanohttpd in my Android project as a temporary isolation layer (due to server side not being mature enough yet). I have isolated the nanohttpd server in an Android Service, which I start from my custom Application object once it's created. This way nanohttpd is not bound to the lifecycle of any particular Activity but can live rather independent of the overall application logic and component life cycles.
The Problem
Now, (almost) everything is working nice and dandy: I can start nanohttpd and perform some initial login requests, my expected mock response is even delivered. When I perform my first "GET" request, though, nanohttpd throws a 400 Bad request status at me, but only the first time. If I back out of the Activity being responsible for the particular "GET" request, and launch it again (from the home screen), it delivers the expected payload with a 200 status, flawlessly.
What Have I Done So Far
I have had a closer look at the nanohttpd source code, trying to track down where and why this 400 status is set. It's not that many places this status code is used. Roughly speaking only here, here and here. Since I'm not dealing with multipart content, I'm left with the first and third "here". But - of course - I can not for my life find neither the root cause of the 400 status, nor which exact block is causing the state for me. When I debug the code, everything works just peachy.
Some Code
This is roughly what my nanohttpd Service (MyNanoHttpdService) looks like:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (ACTION_START.equals(intent.getAction())) {
String errorMessage = null;
if (myNanoHttpd == null) {
String hostUrl = intent.getStringExtra(EXTRA_HOST);
Uri uri = Utils.notEmpty(hostUrl) ? Uri.parse(hostUrl) : Uri.EMPTY;
myNanoHttpd = new MyNanoHttpd(this, uri.getHost(), uri.getPort(), null);
}
if (!myNanoHttpd.isAlive()) {
try {
myNanoHttpd.start();
} catch (IOException e) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
e.printStackTrace(printWriter);
errorMessage = stringWriter.toString();
stopSelf();
}
}
final ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RESULT_LISTENER);
if (resultReceiver != null) {
int status = myNanoHttpd.isAlive() ? CODE_SUCCESS : CODE_FAILURE;
Bundle bundle = new Bundle();
bundle.putString(EXTRA_MESSAGE, errorMessage);
resultReceiver.send(status, bundle);
}
}
return Service.START_STICKY;
}
And this is how I start the service from my custom Application object, initialize my client side state and fetch some content:
#Override
public void onCreate() {
super.onCreate();
// Yes, that is a Java 8 Lambda you see there!
MyNanoHttpdService
.start(this, "http://localhost:8080")
.withStartupListener((status, message) -> {
if (status == 0) {
// POST REQUEST: Works like a charm
myNetworkHelper.login();
// GET REQUEST: Always fails on first launch
myNetworkHelper.getContent();
} else {
Log.e("LOG_TAG", "Couldn't start MyNanoHttpd: " + message);
}
});
}
It's safe to assume that the wrapping convenience code (the .withStartupListener(...) - which essentially wraps a ResultReceiver used by the above Service - and the myNetworkHelper object) works as expected. Also, in production, the getContent() call would be made from an Activity or Fragment, but for the sake ease I have moved it to the Application for now.
I may have found the root cause for my issue, and possibly even a workaround for the moment.
If I'm correct in my investigation, the issue was caused by unconsumed data from a previous (POST) request, contaminating the current (POST) request.
This line in the NanoHTTPD code base (the header parsing block in the NanoHTTPD.HTTPSession.execute() method, just before calling through to any custom serve(...) method - the third "here" in my question above) was the very line where the HTTP 400 status code was thrown, and just as the code suggests, there was no proper value for the "method" header.
The value - which I expected to be "POST" in clear text - was contaminated with parts of the JSON content body from the previous request. As soon as I realized this, I tried to consume the entire request body in my custom MyNanoHttpd.serve(IHTTPSession session) method, like so:
#Override
public Response serve(IHTTPSesion session) {
InputStream inputStream = session.getInputStream();
inputStream.skip(inputStream.available());
// or
// inputStream.skip(Long.MAX_VALUE);
// or even
// inputStream.close();
...
}
This didn't work, though, as I kept getting various exceptions. I ended up gently modifying the NanoHTTPD code, safely closing the input stream in the finally block of the very NanoHTTPD.HTTPSession.execute() method instead.
I'm, nonetheless, considering reaching out to the NanoHTTPD community to discuss a suitable and sustainable solution.

Categories

Resources