I have this bit of code:
public static void main(String[] args) throws UnirestException {
ArrayList<Stock> listStock
= getAllAvailableStocks("https://api.iextrading.com/1.0/ref-data/symbols");
//doing more actions after the one before, using the data from the listStock etc.
}
private static ArrayList<Stock> getAllAvailableStocks(String url) {
ArrayList<Stock> stocks = new ArrayList<Stock>();
Future<HttpResponse<JsonNode>> future = Unirest.get(url)
.header("accept", "application/json")
.asJsonAsync(new Callback<JsonNode>() {
public void failed(UnirestException e) {
System.out.println("The request has failed");
}
public void completed(HttpResponse<JsonNode> response) {
ObjectMapper objectMapper = new ObjectMapper();
try {
listStock = objectMapper.readValue(response.getRawBody(), new TypeReference<List<Stock>>(){});
} catch (Exception e) {
System.out.println("all is fucked");
}
return listStock;
}
public void cancelled() {
System.out.println("The request has been cancelled");
}
});
}
I am a newbie in java, i want to do the following:
1) I want to do async call to get and extract a list of stocks, only after the request completed i want to do the next things in the main method.
2) How do i extract data from the method i built so i can use the data outside of the method?
3) If i need to do the following:
getAllAvailableStocks("https://api.iextrading.com/1.0/ref-data/symbols",new Callback<JsonNode>() {
public void failed(UnirestException e) {
System.out.println("The request has failed");
}
public void completed(HttpResponse<JsonNode> response) {
ArrayList<Stock> listStock = new ArrayList<Stock>();
ObjectMapper objectMapper = new ObjectMapper();
int code = response.getStatus();
System.out.println(code);
try {
listStock = objectMapper.readValue(response.getRawBody(), new TypeReference<List<Stock>>(){});
} catch (Exception e) {
}
System.out.println(listStock);
}
public void cancelled() {
System.out.println("The request has been cancelled");
}
});
}
private static Future<HttpResponse<JsonNode>> getAllAvailableStocks(String url,Callback<JsonNode> cb) {
return Unirest.get(url)
.header("accept", "application/json")
.asJsonAsync(cb);
}
Or something of that sort, it makes the code horrible, and when i want to do much more async requests after, i have a callback hell here, is there any way to avoid it? what are my options here?
I think your are mixing up asnychronous and synchronous.
If you
want to do async call to get and extract a list of stocks, only after the request completed I want to do the next things in the main method
then you actually want to perform a synchronous call.
An asynchronous call would be to perform the request, then doing other things (not related to the request) and at some point in the future you get the result of the request and handle it.
To perform a synchronous call, which is probably what you want, try to adapt your code like this:
private static ArrayList<Stock> getAllAvailableStocks(String url) {
ArrayList<Stock> stocks = new ArrayList<Stock>();
Future<HttpResponse<JsonNode>> future = Unirest.get(url)
.header("accept", "application/json")
.asJsonAsync(new Callback<JsonNode>() {
public void failed(UnirestException e) {
System.out.println("The request has failed");
}
public void completed(HttpResponse<JsonNode> response) {
System.out.println("The request succeeded");
}
public void cancelled() {
System.out.println("The request has been cancelled");
}
});
HttpResponse<JsonNode> response = future.get(); // NOTE: This call is blocking until the request is finished
if (response != null && response.getStatus() == 200) {
JsonNode body = response.getBody();
// TODO Parse body and add items to `stocks`
}
return stocks;
}
This method can be used like this:
ArrayList<Stock> stocks = getAllAvailableStocks(...);
stocks.forEach(x -> System.out.println(x));
Edit
If you want to handle the result asynchronously without providing callbacks, you could use a CompletableFuture. Consider the following snippet as a starting point which does not handle unsuccessful calls.
private static CompletableFuture<ArrayList<Stock>> getAllAvailableStocks(String url) {
CompletableFuture<ArrayList<Stock>> result = new CompletableFuture<>();
Future<HttpResponse<JsonNode>> future = Unirest.get(url)
.header("accept", "application/json")
.asJsonAsync(new Callback<JsonNode>() {
public void failed(UnirestException e) {
System.out.println("The request has failed");
}
public void completed(HttpResponse<JsonNode> response) {
System.out.println("The request succeeded");
ArrayList<Stock> stocks = new ArrayList<Stock>();
if (response != null && response.getStatus() == 200) {
JsonNode body = response.getBody();
// TODO Parse body and add items to `stocks`
}
result.complete(stocks);
}
public void cancelled() {
System.out.println("The request has been cancelled");
}
});
return result;
}
The method can be used as follows:
CompletableFuture<ArrayList<Stock>> stocksFuture = getAllAvailableStocks(...);
stocksFuture.thenAccept((stocks) -> {
// NOTE: This will be called after and only if the request succeeded
stocks.forEach(x -> System.out.println(x));
});
System.out.println("This is probably executed before the above request finished.");
Thread.sleep(10000); // If you are calling from your `main` method: Prevent JVM exit
Related
OkHttp is usually asynchronous. A regular call looks like this:
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
} else {
// do something wih the result
}
}
}
When the message arrives, just do something with it. But I want to use it as a blocking getter. Something like:
public void exampleMethod() {
MyDTO myDto = makeOkHttpCall.getData();
// do something with myDto Entity
}
But all I can find is that I could add code into onResponse(). But that is still asynchronous. Any ideas how to change that?
Instead of enqueue you can use execute to execute a request synchronously.
See example from the OkHttp documentation:
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
(OkHttp documentation: https://square.github.io/okhttp)
Just look at the OkHttp recipes on their website.
You will find for example :
Synchronous Get (.kt, .java)ΒΆ
Asynchronous Get (.kt, .java)
The example for Synchronous Get in Java being :
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
}
MainActivity.java
String[] captcha = new String[2];
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getMessle();
}
});
private void getMessle() {
captcha[0] = "before";
captcha[1] = "before2";
RequestClass rc = new RequestClass();
captcha = rc.getCaptcha();
tv.setText(captcha[1]);
}
RequestClass.java
String[] saResult = new String[2];
public String[] getCaptcha() {
httpUrl = new HttpUrl.Builder()
.scheme(strScheme)
.host(strHost)
.addPathSegments(path)
.build();
request = new Request.Builder()
.url(httpUrl)
.addHeader(header, key)
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, Response response) throws IOException {
saResult[0] = response.header("Set-Cookie");
try {
jsonObject = new JSONObject(response.body().string());
saResult[1] = jsonObject.getString("image_url");
} catch (JSONException je) {
}
}
});
return saResult;
}
I've checked and it doesn't seem like any exceptions are caught. I also know that my application gets a response as well and the datas are definitely there in onResponse(). After some extended testing, it looks like my array is assigned before the response arrives.
captcha = rc.getCaptcha();
You call that right after you enqued a request. The request is not executed then nor is there a result. All goes asynchronous.
Only in onResponse() you will get the result. A long time later. And it is in onResponse() that you should handle the result by displaying it in a text view for instance.
So I've been trying to find a clean way of solving this issue for a while. Here's REST API class:
public class RestClient {
private final IJsonParser jsonParser;
private IServerUtils serverUtils;
private String baseApiUrl;
private OkHttpClient okHttpClient;
#Inject
public RestClient(OkHttpClient okHttpClient, IJsonParser jsonParser, IServerUtils serverUtils, #Named("baseApiUrl") String baseApiUrl) {
this.okHttpClient = okHttpClient;
this.jsonParser = jsonParser;
this.serverUtils = serverUtils;
this.baseApiUrl = baseApiUrl;
}
public Single<String> get(String route) {
if (serverUtils.isThereInternetConnection()) {
Request request = new Request.Builder()
.get()
.url(baseApiUrl + route)
.build();
final Call call = okHttpClient.newCall(request);
return Single.create(emitter -> {
execute(call, emitter);
});
} else {
return Single.error(new NetworkException());
}
}
public Single<String> post(String route, RequestBody requestBody) {
if (serverUtils.isThereInternetConnection()) {
Request request = new Request.Builder()
.post(requestBody)
.url(baseApiUrl + route)
.build();
final Call call = okHttpClient.newCall(request);
return Single.create(emitter -> execute(call, emitter));
} else {
return Single.error(new NetworkException());
}
}
public Single<String> postNoCache(String route, RequestBody requestBody) {
if (serverUtils.isThereInternetConnection()) {
Request request = new Request.Builder()
.post(requestBody)
.url(baseApiUrl + route)
.cacheControl(CacheControl.FORCE_NETWORK)
.build();
final Call call = okHttpClient.newCall(request);
return Single.create(emitter -> execute(call, emitter));
} else {
return Single.error(new NetworkException());
}
}
Response postRaw(String route, RequestBody requestBody) throws IOException {
Request request = new Request.Builder()
.post(requestBody)
.url(baseApiUrl + route)
.build();
return okHttpClient.newCall(request).execute();
}
public RequestBody convertHashMapToRequestBody(HashMap<String, String> params) {
FormBody.Builder builder = new FormBody.Builder();
for (String key : params.keySet()) {
builder.add(key, params.get(key));
}
return builder.build();
}
private void execute(Call call, SingleEmitter<String> emitter) throws IOException {
try {
Response response = call.execute();
String responseJson = response.body().string();
if (response.isSuccessful()) {
emitter.onSuccess(responseJson);
} else {
handleErrors(emitter, response, responseJson);
}
} catch (InterruptedIOException e) {
// This is my issue.
}
}
private void handleErrors(SingleEmitter<String> emitter, Response response, String responseJson) {
if (response.code() == 500 && responseJson != null) {
ApiError error = jsonParser.parseApiError(responseJson);
emitter.onError(new ApiException(error.getErrorMsg(), error.getError(), error.getUri()));
} else {
emitter.onError(new NetworkException("timeout"));
}
}
So in my execute() method I'd like to somehow maybe get some sort of callback when my upstream observer is destroyed, so I can cancel my network request. Currently what seems to happen is the thread that the Single is running on gets an interrupt, which throws an Exception, I'm not sure if this stops execution though. Is there a clean way of doing that? The only thing I've seen that you can do is the emitter.isDisposed(), which I can't see a normal usecase, since everything is synchronous in here and by the time I check that value it's most likely too late.
any help with this would be greatly appreciated.
I'm new on Android development and I'm learning how to use MVP pattern correctly in recently.
Now I'm facing a tricky problem, hope can get some helpful suggestion or solution from here.
First, here is my presenter
public class MVPPresenter {
private MVPView mvpView;
public MVPPresenter(MVPView mvpView) {
this.mvpView = mvpView;
}
public void loadData() {
mvpView.startLoading();
final List<MVPModel> list = new ArrayList<>();
//the part that I trying to extract starts here.
Call call = DataRetriever.getDataByGet(URLCombiner.GET_FRONT_PAGE_ITEMS);
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
mvpView.errorLoading();
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
try {
JSONObject result = new JSONObject(response.body().string());
int errorCode = result.getInt("ErrorCode");
if (errorCode == 0) {
JSONArray value = result.getJSONObject("Value").getJSONArray("hot");
for (int i = 0; i < value.length(); i++) {
MVPModel mvpModel = new MVPModel();
String name = null;
String image = null;
try {
name = value.getJSONObject(i).getString("title");
image = URLCombiner.IP + value.getJSONObject(i).getString("pic");
} catch (JSONException e) {
e.printStackTrace();
}
mvpModel.setName(name);
mvpModel.setImage(image);
list.add(mvpModel);
}
if (list.size() > 0) {
mvpView.successLoading(list);
mvpView.finishLoading();
} else {
mvpView.errorLoading();
}
} else {
mvpView.errorLoading();
}
} catch (JSONException e) {
e.printStackTrace();
}
} else {
mvpView.errorLoading();
}
}
});
//the part that I trying to extract ends here.
}
}
As you can see, I'm trying to extract the part which is using OKHttp libs into a class (I call it data manager) which I hope it can handle every task between server and client. But here's the thing, when I trying to pass the result from the data manager to presenter, I got NullPointException because of the async mechanism.
I would like to know how to passing the data, which is come from server in async, to the presenter when the data has finish downloading.
And here's my ideal data manager, I know this might looks stupid but I think this can make my problem more clearly.
public class LoadServerData {
private static JSONArray arrayData = new JSONArray();
public static JSONArray getServerData() {
Call call = DataRetriever.getDataByGet(URLCombiner.GET_FRONT_PAGE_ITEMS);
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
try {
JSONObject result = new JSONObject(response.body().string());
int errorCode = result.getInt("ErrorCode");
if (errorCode == 0) {
arrayData = result.getJSONObject("Value").getJSONArray("hot"); //the data I would like to return.
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
});
return arrayData; //this is gonna be an empty data.
}
}
I've reading some article that might can solve my problem, but still not getting any fine answer. Perhaps I've using wrong keyword I think. Hopes you guys can give me some ideas or solutions to help me or inspire me.
P.S. version of OKhttp libs is 3.7.0
Create a simple Listener so it can be called whenever the server call finishes:
public class LoadServerData {
public static interface Listener {
public void onSuccess(JSONArray data);
public void onError(Exception error);
}
public static void getServerData(Listener listener) {
Call call = DataRetriever.getDataByGet(URLCombiner.GET_FRONT_PAGE_ITEMS);
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
listener.onError(e);
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
try {
JSONObject result = new JSONObject(response.body().string());
int errorCode = result.getInt("ErrorCode");
if (errorCode == 0) {
JSONArray arrayData = result.getJSONObject("Value").getJSONArray("hot"); //the data I would like to return.
listener.onSuccess(arrayData);
} else {
listener.onError(...);
}
} catch (JSONException e) {
e.printStackTrace();
listener.onError(e);
}
} else {
listener.onError(...);
}
}
});
}
}
Since I got some bad reviews I am rewriting this question...
I have an HTTP REST server and a client (Android app). I have programmed several APIs that work just fine, however there is one that is giving me a 400 error, and if I put a breakpoint in the server, it does not even triggers it. So, I would like to understand why it fails :( ...
It is very simple, I have a value object called Alarm with a few attributes, that I want to POST to the server for registration of object in the database.
This is the output:
Callback failure for call to http://10.0.0.3:8080/...
java.io.IOException: Unexpected code Response{protocol=http/1.1, code=400, message=, url=http://10.0.0.3:8080/BiTrack_API/api/assets/registerAlarm}
at it.bitrack.fabio.bitrack.AlarmView$2$1.onResponse(AlarmView.java:438)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:135)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
This is my client side Android button listener:
View.OnClickListener addAlarmAction = new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
alarm.setThreshold(Float.parseFloat(thresholdEditText.getText().toString()));
String alarmJson = j.makeJsonBodyForAlarmRegistration(alarm);
tagLinearLayout.setVisibility(view.GONE);
operatorLinearLayout.setVisibility(view.GONE);
thresholdLinearLayout.setVisibility(view.GONE);
assetSpinner.setSelection(0);
r.attemptAddNewAlarm(alarmJson,
new Callback() {
#Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override public void onResponse(Call call, Response response) throws IOException {
try (final ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
final String responseBodyString = responseBody.string();
final int resultCode = response.code();
try {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Log.i("BiTrack", "attemptAddNewAlarm RESULT: " + resultCode);
executeAlarmRegistration(resultCode);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
};
This is the code where I generate the Json body for the POST in the client:
public String makeJsonBodyForAlarmRegistration (Alarm alarm) {
Gson gson = new Gson();
String jsonAlarm = gson.toJson(alarm);
return jsonAlarm;
}
This is the actual POST code in the client (Android) side:
public void attemptAddNewAlarm(String json, Callback callback) throws Exception {
final OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(WEB_SERVER + "BiTrack_API/api/assets/registerAlarm")
.post(body)
.build();
client.newCall(request).enqueue(callback);
}
This is my server side code:
#POST
#Path("/registerAlarm")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response registerAlarm(Alarm alarm) {
System.out.println("Received API Call: registerAlarm for alarm tagId: " + alarm.getTagId() + " operatorId: " + alarm.getOperatorId() + " treshold: " + alarm.getThreshold());
DataProcessor dp = new DataProcessor();
AssetUpdateDAO aDAO = new AssetUpdateDAO();
ArrayList<Alarm> customerAlarms = aDAO.getUserAlarmsForAsset(alarm.getUserId(), alarm.getAssetId());
if (dp.isNewAlarmDuplicate(customerAlarms, alarm)) {
return Response.status(480).build(); // duplicated error
} else {
int response = aDAO.insertAssetUserAlarm(alarm.getUserId(), alarm.getAssetId(), alarm.getTagId(), alarm.getOperatorId(), alarm.getThreshold());
if (response == -5) {
return Response.status(484).build(); // something went wrong while inserting alarm into db
} else {
return Response.status(200).build();
}
}
}
This is my Alarm value object (identical class in client and server):
public class Alarm {
public Alarm() {
}
protected int id;
protected int userId;
protected int assetId;
protected int tagId;
protected int operatorId;
protected float threshold;
protected String networkAssetCode;
public String getNetworkAssetCode() {
return networkAssetCode;
}
public void setNetworkAssetCode(String networkAssetCode) {
this.networkAssetCode = networkAssetCode;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getAssetId() {
return assetId;
}
public void setAssetId(int assetId) {
this.assetId = assetId;
}
public int getTagId() {
return tagId;
}
public void setTagId(int tagId) {
this.tagId = tagId;
}
public int getOperatorId() {
return operatorId;
}
public void setOperatorId(int operatorId) {
this.operatorId = operatorId;
}
public float getThreshold() {
return threshold;
}
public void setThreshold(float threshold) {
this.threshold = threshold;
}
}
I really appreciate any help...
In order to help you, the endpoint code is required. Now it is even unclear what technology stack is used for your API.
But from the information that is present... The endpoint considers your json as invalid.
400 Bad Request
The request could not be understood by the server due to malformed
syntax. The client SHOULD NOT repeat the request without
modifications.
In jax-rs the payload is first deserialized before it reaches the method that is bound to the url en http method.
Possibly the deserializing is failing and it never reaches the breakpoint you set.
What would be interesting is the following:
logs or exception from the server. The client exception is not that helpful, since the server returns this response.
the actual (json) payload that is send over the wire.
what deserialization mechanism is used at the server end? Reflection or did you make your own deserializer?
I found the issue! After 48 hours looking for the impossible, discovered that I had done a small update at the object attribute at the server side that had not been replicated in the client side...