Iterating over async events, need to pass an object - java

I have the following problem:
I have implemented an API to my server returning objects, which is all fine and well. All the calls to the API starts an AsyncTask, and returns a result in a method. Problem is, at one point (I need this is bad coding, but disregard that for a minute) over a list of returned Events. These Events are returned from one AsyncTask call, and used for another AsyncTask call, and I need to pass the event I'm iterating over to the next API call as seen here:
api.retrieveEvents(new GetResponseCallback<Event>() {
#Override
public void onDataReceived(ArrayList<Event> list) {
for (Event e : list) {
api.retrieveReservationsForEvent(e, new GetResponseCallback<EventRegistration>() {
#Override
public void onDataReceived(ArrayList<EventRegistration> list) {
Log.d("We are in event", e.getEventName());
}
});
Now obviously, Java won't allow you to use a local variable outside its classes scope, without it being declared final. That's obviously not gonna work here, and I can't assign the event to a field variable either, since that will just result in the final event I'm iterating over to be the one the retrieveReservationsForEvent call all the use. Any ideas of what to do?
retrieveReservationsForEvent looks like this:
public void retrieveReservationsForEvent(Event event, final GetResponseCallback<EventRegistration> callback) {
final ArrayList<EventRegistration> eventRegistrations = new ArrayList<>();
String restUrl = SERVER_NAME + REST_EVENTREGISTRATION + event.getId();
new GetTask(restUrl, new RestTaskCallback() {
#Override
public void onComplete(String response) {
try {
// TODO probably throws an exception if there is only one attendant...
JSONArray jsonArray = new JSONArray(response);
for(int i = 0; i < jsonArray.length(); i++) {
JSONObject tempEventRegistrationJSON = (JSONObject) jsonArray.get(i);
long dateInMillis = tempEventRegistrationJSON.getLong("timeOfArrival");
Date date = new Date(dateInMillis);
EventRegistration tempEventRegistration = new EventRegistration(tempEventRegistrationJSON.getInt("id"),
tempEventRegistrationJSON.getInt("eventNightId"), tempEventRegistrationJSON.getInt("guestId"),
date, tempEventRegistrationJSON.getInt("numberOfGuests"));
tempEventRegistration.setGuestName(tempEventRegistrationJSON.getString("guestName"));
tempEventRegistration.setGuestPhoneNumber(tempEventRegistrationJSON.getInt("phoneNumber"));
tempEventRegistration.setGuestMail("mail");
eventRegistrations.add(tempEventRegistration);
callback.onDataReceived(eventRegistrations);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}).execute();
}
GetTask is merely doing a GET request, returning the response.

Are you sure that changing it to:
for (final Event e : list) {
doesn't work? Does it give you an error during compilation?

Related

How to get data back from an okhttp call?

I have a method which calls an external API using okhttp library on android, I'm able to access the data that comes back inside that method/thread but I'm not able to return the data or use it somewhere else. What's the problem?
I have tried putting the data in another class (extended from AsyncTask) and it still didn't work.
public class DisplayImage extends AppCompatActivity {
ImageView imageView;
TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_display_image);
imageView = findViewById(R.id.mImageView);
textView = findViewById(R.id.textView);
Bitmap bitmap = BitmapFactory.decodeFile(getIntent().getStringExtra("image_path"));
imageView.setImageBitmap(bitmap);
String imagePath = getIntent().getStringExtra("image_path");
try {
//map returned here
HashMap<String, double[]> map = getCropInfo(imagePath);
//This text view doesn't update
textView.setText(String.valueOf(map.get("ID")[0]));
} catch (Exception e) {
e.printStackTrace();
}
}
HashMap getCropInfo(String imageUri) throws Exception {
final HashMap<String, double[]> map = new HashMap<>();
OkHttpClient client = new OkHttpClient();
MediaType MEDIA_TYPE_PNG = MediaType.parse("image/jpg");
File file = new File(imageUri);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("image", file.getName(), RequestBody.create(MEDIA_TYPE_PNG, file))
.build();
Request request = new Request.Builder()
.header("Prediction-Key", "") //predictionkey hidden
.header("Content-Type", "application/octet-stream")
.url("https://westeurope.api.cognitive.microsoft.com/customvision/v3.0/Prediction/7f5583c8-36e6-4598-8fc3-f9e7db218ec7/detect/iterations/Iteration1/image")
.post(requestBody)
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
public void onResponse(Call call, final Response response) throws IOException {
// Read data on the worker thread
final String responseData = response.body().string();
// Run view-related code back on the main thread
DisplayImage.this.runOnUiThread(new Runnable() {
#Override
public void run() {
try {
JSONObject jsonObject = new JSONObject(responseData);
JSONArray jsonArray = jsonObject.getJSONArray("predictions");
double highestIDProbability = 0;
double highestVoltageProbability = 0;
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject tempObject = jsonArray.getJSONObject(i);
if(tempObject.getString("tagName").equals("ID") && tempObject.getDouble("probability") > highestIDProbability) {
highestIDProbability = tempObject.getDouble("probability");
map.put("ID", getCoordinatesPixels(tempObject));
}
else if(tempObject.getString("tagName").equals("Voltage") && tempObject.getDouble("probability") > highestVoltageProbability) {
highestVoltageProbability = tempObject.getDouble("probability");
map.put("Voltage", getCoordinatesPixels(tempObject));
}
}
//setting text view works from here.
//textView.setText(String.valueOf(map.get("ID")[0]));
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
});
//I am returning map
return map;
}
static double[] getCoordinatesPixels(JSONObject object) {
double[] arr = new double[4];
try {
JSONObject innerObject = object.getJSONObject("boundingBox");
arr[0] = innerObject.getDouble("left");
arr[1] = innerObject.getDouble("top");
arr[2] = innerObject.getDouble("width");
arr[3] = innerObject.getDouble("height");
} catch (JSONException e) {
e.printStackTrace();
}
return arr;
}
}
I need the map to return so I can use the data externally.
I believe you're running into an issue related to the asynchronous nature of OkHttp and network requests in general. When you do a new call, that call is queued and handled asynchronously. This means that the code will most likely execute return map; before the asynchronous call has completed and before the callback modifies the map. If you need access to the map outside of the scope of the callback you have two main options.
Make the call blocking. This essentially means that you will have to force the function to stall until the OkHttp callback is triggered before return map; occurs. I would absolutely not recommend doing this as it defeats the entire purpose of moving long running tasks to other threads.
Call a function inside the onResponse() callback. Construct the map inside the callback itself, then just call a function with that map as a parameter to handle any operations you need to do on that map. Alternatively you could also make the map a global variable so you can access it from practically anywhere.
On a sidenote, if this data is going to be used to propagate changes back to the UI or other program state, I would recommend using a ViewModel (it's a model object that holds data and can outlive Activity lifecycles) paired with something like MutableLiveData (which is a data wrapper that makes basically anything observable).
Using such a setup, you would have your map object inside your ViewModel. You would then register an observer from any context (Activity, Fragment, etc) where you need to know about updates on the map. Finally, in the callback, you would just need to update the ViewModel's map. This would automatically notify any registered observers.
Good luck!

ArrayAdapter Help, populating with string array returned from method

I'm working my way through Head First Android Development, and I am having an issue. I'm trying to use the following code and populate the listview with an array returned from a method in another class, but I am getting errors and don't understand why. It only seems to give me errors if I try and call the method in the new ArrayAdapter, like so....
public class ListBikeStands extends ListActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listStands = getListView();
ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
BikesStands.getStandName());
listStands.setAdapter(listAdapter);
}
The class/method it's calling is as follows...
public class BikeStands {
private static OkHttpClient client = new OkHttpClient();
private static final String key = "key";
private static final String contract = "contract";
public static String getJSON(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
return response.body().string();
}
private static String[] getStandName() {
String json = null;
try {
json = getJSON("https://api.jcdecaux.com/vls/v1/stations?contract=" + contract + "&apiKey=" + key);
} catch (IOException e) {
e.printStackTrace();
}
Gson gson = new Gson();
DBikesStation[] bikesAPI = gson.fromJson(json, DBikesStation[].class);
String[] bikeStands = new String[bikesAPI.length];
for (int i = 0; i < bikesAPI.length; ++i) {
bikeStands[i] = bikesAPI[i].getName();
}
;
return bikeStands;
}
I'm getting the following error...
java.lang.RuntimeException: Unable to start activity
ComponentInfo{package......dublinbikes.ListBikeStands}:
android.os.NetworkOnMainThreadException
I do have ListBikeStands as an activity in the AndroidManifest.xml.
Any help is much appreciated. Thanks.
Your usage of OkHTTPClient is not quite correct. You've made a synchronous request on the UI Thread, therefore your error.
public static String getJSON(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
return response.body().string();
}
In order to make an asynchronous request, in the background, try something like so. Pass in a Callback and use enqueue.
Important note: If you ever think you need return in an asynchronous method, that's typically not correct. See that I changed the method to a void type. Callbacks are how to return variables.
public static void getJSON(String url, Callback callback) throws IOException {
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(callback);
}
To call this, define the callback - Shown as a variable here, but can also be in-lined into the method call like a View.OnClickListner would be for a button.
You should define this inside the Activity class, since you will be updating the adapter.
final Callback bikeStandCallback = new Callback() {
#Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
String body = response.body().string();
Gson gson = new Gson();
DBikesStation[] bikesAPI = gson.fromJson(body, DBikesStation[].class);
for (int i = 0; i < bikesAPI.length; ++i) {
adapter.add(bikesAPI[i].getName());
}
}
};
And pass that in when you need it.
public class ListBikeStands extends ListActivity {
private ListView listStands;
private ArrayAdapter<String> listAdapter;
// TODO: Define above bikeStandCallback here
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listStands = getListView();
ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1);
listStands.setAdapter(listAdapter);
// TODO: Get url value
BikeStands.getJSON(url, bikeStandCallback);
}
}
And, in my opinion, this is very flexible in the fact that you can define multiple callbacks for various data, call BikeStands.getJSON again with a different parameter, then do whatever you want with that request.
Overall, this is somewhat what Retrofit does, except the Gson converter can be added on for you.
You are setting the adapter in the main thread. In the adapter, you are calling the method "getJSON()" which does some networking. However, you are not allowed to do networking on the main thread. You need to grab the JSON in a separate thread - use Asynctask for that (get the JSON in "doInBackground()" and then set the adapter in "onPostExecute()")
The error you're getting is thrown not while you're populating the adapter but because you're trying to do network operation on the main thread.
You have to do network operation on a different thread, you could use AsyncTask, you could find other information about that error in this question
Networking on main thread is forbidden because is usually asynchronous and resources intensive, so it could generate an ANR (Application not responding)

Waiting requests functionality issue within Volley library (PriorityBlockingQueue.java)

I have a problem with the waiting requests functionality in the volley library. The debugging led me to the AbstractQueue class in java.util where an element is being added (according to some values in the method that indicate a successful addition to the queue) and simultaneously - not being added(according to the 0 elements in the queue - that don't change their value). The adding method is synchronized. Bellow you can find a detailed description of the situation and my research so far. I will be really thankful if you have a look at them and share if you have any idea what is happening.
I try to automatically retry requests upon any kind of error ( for example - when there is no connection, or the server name is not correct ).
The error handler of a request adds the request back to the static singleton RequestQueue of my app.
RetriableRequestWraper.java
m_request = new StringRequest(
method,
url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
handleResponse(response);
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError volleyError) {
handleError(volleyError);
}
});
public void handleError(VolleyError volleyError)
{
Log.d("Request: ", m_request.toString());
Log.d("handleError: ", volleyError.toString());
if(retriesCount<3)
{
executeRequest();
++retriesCount;
}
else
{
retriesCount = 0;
}
}
public void executeRequest()
{
RequestsManager.getInstance().executeRequest(m_request);
}
public void executeRequest(Request request)
{
Log.d("executeRequest ","m_requestQueue.add(request)");
m_requestQueue.add(request);
}
RequestManager.java
public void executeRequest(Request request)
{
Log.d("executeRequest ","m_requestQueue.add(request)");
m_requestQueue.add(request);
}
This approach doesn't work and when debugging inside the volley library I come to the point where the request could not be added to the mCacheQueue of the RequestQueue class, because the cacheKey of the reuqest is present in the mWaitingRequests Map. So the request is added in the queue in mWaitingRequests map, corresponding to its key. When the previous request is finished - the new one is not added to the queue although these lines are being executed in the RequestQueue class:
synchronized(this.mWaitingRequests) {
String cacheKey1 = request.getCacheKey();
Queue waitingRequests1 = (Queue)this.mWaitingRequests.remove(cacheKey1);
if(waitingRequests1 != null) {
if(VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", new Object[]{Integer.valueOf(waitingRequests1.size()), cacheKey1});
}
this.mCacheQueue.addAll(waitingRequests1);
}
}
When debugging further this line
this.mCacheQueue.addAll(waitingRequests1);
In the AbstractQueue.java (class in java.util ) the element is being added to the queue, the "modified" value is true, but throughout the hole time the "this" parameter continues to contain 0 elements.
public boolean addAll(Collection<? extends E> c) {
if (c == null)
throw new NullPointerException("c == null");
if (c == this)
throw new IllegalArgumentException("c == this");
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
Inside the offer(E e) method of PriorityBlockingQueue.java the execution of the program stops at line 453.
l452 siftUpUsingComparator(n, e, array, cmp);
l453 size = n+1;
Obviously the returned value is true, but the element is not added. My debugger could not get into the method that adds the element - siftUpUsingComparator(n, e, array, cmp);
I am going to add a timer before retrying my request, and will construct a new one. So I am not really interested in a workaround, I want to understand what and how is happening in this situation. Do you have any idea as to what could be the reason behind this?
The issue is that you try to add the same Request instance once again to the queue it has been added to. This messes up with the queue and the Request itself as it has states. For example if you simply enable markers you'll have a crash. The solution is to either just use the default retry policy or clone the requests.

List appears as null if used outside the local variable scope in Java

Background: I am creating a turnbased multiplayer game. The user is randomly matched with another user. Upon matching, the user is given a list of words, which he will need to use for the reminder of the game. This list of words is randomly chosen from a much larger list.
Problem: I get the index out of bounds exception when I run the app (exception points to line where mCopy is used outside the query.getFirstInBackground code block. When I debugged the app and set breakpoints on mCopy(list of words available to user) , it showed an empty list. It should be noted that the wordsList is appearing in the backend, and if I execute the all the code in the query.getFirstInBackground block, I do not have a problem. However, what I want is that once mCopy retrieves the list from Parse, I can use mCopy anywhere in the class.
Here is the relevant code:
public class PlayGameActivity extends Activity {
protected LinkedList<String> mCopy; //This is a global variable where words are stored
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play_game);
mCopy = new LinkedList<String>();
Intent cameFromGIPActivity = getIntent();
mCameFromGIPActivity = cameFromGIPActivity.getStringExtra(ExTRA_GamesInProgress);
// if this is null, that means opp came from FindingOpp Activity
if (mCameFromGIPActivity == null) {
//use the opponent name as a key to get selected words(value)
ParseQuery<ParseObject> query = ParseQuery.getQuery(ParseConstants.CLASS_GAMES_IN_PROGRESS);
query.whereMatches(ParseConstants.KEY_OPPONENT_NAME, mOpponentName);
query.getFirstInBackground(new GetCallback<ParseObject>() {
#Override
public void done(ParseObject gameInfo, ParseException e) {
if (e == null) {
Log.d(TAG, "success!");
mSavedWordList = gameInfo.getString(ParseConstants.KEY_SELECTED_WORDS);
Gson gson = new Gson();
Type type = new TypeToken<ArrayList<String>>() {
}.getType();
ArrayList<String> finalWordList = gson.fromJson(mSavedWordList, type);
mCopy.addAll(finalWordList);
});
word1 = (TextView) findViewById(R.id.word1); //I get the index out of bound exception here,
word1.setText(mCopy.get(1));
word1.setOnClickListener(new View.OnClickListener());
Edit1: I previously stated that I get no exception in the logCat, I was wrong. I get the index out of bound exception. Apologies.
As stated here, getFirstInBackground retrieves a ParseObject based on your query in a separate thread without blocking/freezing the execution in your current thread. Once the fetch is complete, done() in callback object is invoked.
Even though it looks your statements are ordered one after the other in your code, getFirstInBackground is an Asynchronous operation. So fetch will be running in a different thread, while the remaining statements in your code are executed.
As your mCopy is updated within done method, any code that relies on the update should be executed in the same method like below.
query.getFirstInBackground(new GetCallback<ParseObject>() {
#Override
public void done(ParseObject gameInfo, ParseException e) {
if (e == null) {
Log.d(TAG, "success!");
mSavedWordList = gameInfo.getString(ParseConstants.KEY_SELECTED_WORDS);
Gson gson = new Gson();
Type type = new TypeToken<ArrayList<String>>() {
}.getType();
ArrayList<String> finalWordList = gson.fromJson(mSavedWordList, type);
mCopy.addAll(finalWordList);
/* mCopy is updated here - so use it now*/
word1 = (TextView) findViewById(R.id.word1);
word1.setText(mCopy.get(1));
word1.setOnClickListener(new View.OnClickListener());
});

Synchronous RPC Calls in GWT

(That title alone should cause people to come out of the woodwork to bash me with clubs, but hear me out).
I have a use case where I need to return a value from a asynchronous call. (I'm using GWT-Platform, but the concepts are the same.) I declared a final JavaScriptObject array, then assigned the value within the AsyncCallback. However, I need to return the value, and the method returns before the AsyncCallback completes. Therefore, I need to block somehow until the AsyncCallback completes. I need the returned value in another method, or I'd just do what I need to in onSuccess().
I've tried loops, Timers, and a few other methods with no luck. Can anyone help?
#Override
public JavaScriptObject doGetWhereAmIMarker(final double lng, final double lat) {
final JavaScriptObject[] markerArray = new JavaScriptObject[1]; // ugly hack, I know
dispatch.execute(new GetLocationDescriptionsAction(lng, lat), new AsyncCallback<GetLocationDescriptionsResult>() {
#Override
public void onFailure(Throwable caught) {
caught.printStackTrace();
}
#Override
public void onSuccess(GetLocationDescriptionsResult result) {
Map<String, Location> resultMap = result.getResult();
StringBuffer message = new StringBuffer();
for (String key : resultMap.keySet()) {
message.append(key).append(": ").append(resultMap.get(key)).append("\n");
}
Map tempMap = new HashMap();
tempMap.put("TITLE","Location Information");
tempMap.put("LAT", lat);
tempMap.put("LNG", lng);
tempMap.put("CONTENTS", message.toString());
JavaScriptObject marker = GoogleMapUtil.createMarker(tempMap);
markerArray[0] = marker;
if (markerArray[0] != null) {
GWT.log("Marker Array Updated");
}
}
});
return markerArray[0];
}
UPDATE: As requested, here is the code that calls doGetWhereIAmMarker(). I've tried having a separate native method with the Google Map object (as a JavaScriptObject) as a parameter, but it appears that passing that object between native methods kills the ability to update said object.
public native void initMap(JavaScriptObject mapOptions, JavaScriptObject bounds, JavaScriptObject border, JsArray markerArray, Element e) /*-{
// create the map and fit it within the given bounds
map = new $wnd.google.maps.Map(e, mapOptions);
if (bounds != null) {
map.fitBounds(bounds);
}
// set the polygon for the borders
if (border != null) {
border.setMap(map);
}
// set up the info windows
if (markerArray != null && markerArray.length > 0) {
var infoWindow = new $wnd.google.maps.InfoWindow({
content:"InfoWindow Content Goes Here"
});
for (var i = 0; i < markerArray.length; i++) {
var marker = markerArray[i];
marker.setMap(map);
$wnd.google.maps.event.addListener(marker, 'click', function() {
infoWindow.setContent(marker.content);
infoWindow.open(map, this);
});
}
}
// need to reference the calling class inside the function(), so set a reference to "this"
var that = this;
$wnd.whereAmI=function(lng, lat) {
that.#org.jason.mapmaker.client.view.MapmakerMapViewImpl::whereAmI(DD)(lng,lat);
}
$wnd.google.maps.event.addListener(map, 'click', function(event) {
var lat = event.latLng.lat();
var lng = event.latLng.lng();
$wnd.whereAmI(lng, lat);
});
}-*/;
At some point I had to do something similar but eventually I eliminated that code in favor of asynchronous stuff. Therefore, I can't give exact code that you need to use but only few pointers on how to approach it.
Firstly, this blog describes how to do synchronous AJAX using javascript.
Second, you must provide support for sync calls. The problem is that GWT does not support the parameter that provides synchronous AJAX calls. Most likely is that they don't want to encourage its use. Therefore you would need to use JSNI to add appropriate method to XMLHttpRequest (which you probably would extend) and then to RequestBuilder (also should extend it).
Finally, amend your service using extended RequestBuilder. Something like
((ServiceDefTarget)service).setRpcRequestBuilder(requestBuilder);
And in conclusion - from the same blog post (slightly out of context):
Because of the danger of a request getting lost and hanging the browser,
synchronous javascript isn't recommended for anything outside of
(onbefore)unload event handlers.
I Think its all fate....
We cannot do in Gwt to catch the Response and Send it, because immediately after the request is send the next method starts executing, neither bothering the response
Still however they satisfy us to use the Timers, is what i think...
Timer t = new Timer() {
#Override
public void run() {
Window.alert("Nifty, eh?");
}
};
t.schedule(5000);

Categories

Resources