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!
Related
I try to add data from my object to ArrayList but it's not work.
This code read data from JSON and add to ArrayList in MySQLConnect.java like this.
private ComputerService computerservice;
public static ArrayList<ComputerService> computerServicesArrayList = new ArrayList<>();
private String URL = "http://10.200.100.10/", GET_URL = "android/get_data.php";
public MySQLConnect(){
main = null;
}
public MySQLConnect(Activity mainA){
main = mainA;
}
public List<ComputerService> getData(){
String url = URL + GET_URL;
StringRequest stringRequest = new StringRequest(url, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
showJSON(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(main, error.getMessage().toString(), LENGTH_LONG).show();
}
}
);
RequestQueue requestQueue = Volley.newRequestQueue(main.getApplicationContext());
requestQueue.add(stringRequest);
return computerServicesArrayList;
}
public void showJSON(String response){
String data_mysql = "";
computerServicesArrayList.clear();
try{
JSONObject jsonObject = new JSONObject(response);
JSONArray result = jsonObject.getJSONArray("data");
for(int i=0; i < result.length(); i++){
JSONObject collectData = result.getJSONObject(i);
String id = collectData.getString("id");
String type = collectData.getString("type");
String address = collectData.getString("address");
computerservice = new ComputerService(id, type, address);
computerServicesArrayList.add(computerservice);
}
System.out.println("Size in class MySQLConnect");
System.out.println(computerServicesArrayList.size());
} catch (JSONException e) {
e.printStackTrace();
}
}
The MainActivity.java I show computerServicesArrayList.size() like this.
public static List<ComputerService> computerServicesArrayList;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mySQLConnect = new MySQLConnect(MainActivity.this);
update();
}
public void update(){
computerServicesArrayList = mySQLConnect.getData();
System.out.println("Size in MainActivity");
System.out.println(computerServicesArrayList.size());
}
The output show like this.
Size in MainActivity
0
Size in class MySQLConnect
83
From the code I can print computerServicesArrayList.size() the result is 83 but when I print from MainActivity why it show result 0. How to fix it?
I don't know the Volley framework/classes in detail. But it looks like you are creating an asynchronous request. So your rest-request gets send and when the response comes in your showJSON() method is called.
But you immediatley return the computerServicesArrayList result, which is empty because you don't have your response yet. This is also the reason why the print statement from your MainActivity is executed before the print from your showJSON method.
If you want to wait for the rest-response you have to do synchronous requests.
Maybe this can help you more about Volley and asyn/sync requests:
how to wait the volley response to finish it's work inside intentservice?
Can I do a synchronous request with volley?
But normally you would send an async-request and when you get the response you do your logic (update fields, store something in database, ...).
Your computerServicesArrayList is populated by callback from Volley (new Response.Listener()). This population happens correctly as you have verified. But it does take some time, for the network up/down travel. When your MainActivity's call to mySQLConnect.getData() returns this round trip is not complete yet; so you get an empty list in MainActivity.
The usual solution to this problem is to make the listener call methods in MainActivity. This can be done by making
class MainActivity implements Response.Listener<String> {
/* --- */
#Override
public void onResponse(String response) {
showJSON(response);
}
void showJSON(String response){
// Do the stuff here
}
I am developing a weather app for that I am using dark sky API in which I want to know the weather status of a bunch of locations which I have stored in ArrayList<LatLng>.
I am using OKHttp to parse the JSON data from API, so I tried to loop the whole fetching process inside for loop but it doesn't give the desired output.
private void beginTask(ArrayList<LatLng> arrayLis) {
//arraylis contains list of locations(LatLng)
m = 0;
startTask = true;
for (int i = 0;i<arrayLis.size();i++) {
double latitude = ((LatLng)arrayLis.get(i)).latitude;
double longitude = ((LatLng)arrayLis.get(i)).longitude;
String url = "https://api.darksky.net/forecast/APIKEY/"
+latitude+","+longitude+"?units=si";
LatLng mylatlng = new LatLng(latitude,longitude);
startProcess(url,mylatlng);
Log.i("GGGTT",""+latitude+", "+longitude);
}
}
private void startProcess(String myurl, final LatLng myLatlng){
OkHttpClient httpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(myurl)
.build();
Call call = httpClient.newCall(request);
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, Response response) throws IOException {
String data = response.body().string();
Log.i("DATASS",data);
if (response.isSuccessful()){
try {
getCurrentDetails(data,myLatlng);
} catch (JSONException e){
e.printStackTrace();
}
}
}
});
}
private void getCurrentDetails(String data,LatLng myLatlng) throws JSONException{
JSONObject main = new JSONObject(data);
double la = main.getDouble("latitude");
double lon = main.getDouble("longitude");
JSONObject currently = main.getJSONObject("currently");
String summary = currently.getString("summary");
double temperature = currently.getDouble("temperature");
String icon = currently.getString("icon");
LatLng latLngo = new LatLng(la,lon);
ter.add(latLngo);
weatherInfo.add(icon);
// Output here is not in the same order that I have passed
Log.i("LETSCHECK",""+la+", "+lon +icon);
}
I am passing the values as:
19.21111,73.07729
19.20238,73.06582
19.19383,73.05362
19.18848,73.04221
But the output is not in the same order inside the getCurrentDetails method
19.19383,73.05362
19.20238,73.06582
19.18848,73.04221
19.21111,73.07729
I think the method is not waiting before the previous loop gets completed.
Are there any solutions for getting the weather status of all locations stored in ArrayList without changing its order?
EDIT
Hi, I have gone through this method to fetch data in order and its working fine, thanks but one more problem is that I was expecting to show data 4 times as there are four LatLngs in ArrayList and it's working fine, but when I try to read the fetching data that I have stored in another array it only shows 2 items rather than 4.
private void getCurrentDetails(String data,LatLng myLatlng) throws JSONException{
JSONObject main = new JSONObject(data);
double la = main.getDouble("latitude");
double lon = main.getDouble("longitude");
JSONObject currently = main.getJSONObject("currently");
String summary = currently.getString("summary");
double temperature = currently.getDouble("temperature");
String icon = currently.getString("icon");
//Log.i("LETSCHECK",""+la+", "+lon +icon+",k");
loopi++;
Log.i("LETSCHECK",""+loopi);
if (loopi < arrayList.size()) {
getItAgain();
} else if (loopi == arrayList.size()){
for (String l:weatherInfo){
Log.i("LETSCHECK",l);
//expected 4 items to show but its showing only 2 items
}
}
Log.i("LETSCHECK",""+la+", "+lon +icon);
weatherInfo.add(icon);
}
private void getItAgain() {
double latitude = ((LatLng)arrayList.get(loopi)).latitude;
double longitude = ((LatLng)arrayList.get(loopi)).longitude;
String url = "https://api.darksky.net/forecast/74d8feeda5ecf5ee6667d034778b239d/"
+latitude+","+longitude+"?units=si";
LatLng mylatlng = new LatLng(latitude,longitude);
startProcess(url,mylatlng);
}
The network calls are asynchronous and this the expected behavior that you are having here. If you want to get them sorted as you have passed them, then you might need to have a mechanism of waiting for the network call of the previous one to be completed and then again call the next one. However, this might increase the delay of waiting for the response from the server, as you are not fetching multiple responses from the server concurrently.
If you want to wait for the response to maintain the sorted manner, you might just need to do the following.
// Declare a global variable of the list of your locations.
ArrayList<LatLng> arrayLis;
int i = 0;
// Just start the first task without the loop.
double latitude = ((LatLng)arrayLis.get(i)).latitude;
double longitude = ((LatLng)arrayLis.get(i)).longitude;
String url = "https://api.darksky.net/forecast/APIKEY/"
+latitude+","+longitude+"?units=si";
LatLng mylatlng = new LatLng(latitude,longitude);
startProcess(url,mylatlng);
Now in the getCurrentDetails function, you might just have to do the following.
private void getCurrentDetails(String data,LatLng myLatlng) throws JSONException{
JSONObject main = new JSONObject(data);
double la = main.getDouble("latitude");
double lon = main.getDouble("longitude");
JSONObject currently = main.getJSONObject("currently");
String summary = currently.getString("summary");
double temperature = currently.getDouble("temperature");
String icon = currently.getString("icon");
LatLng latLngo = new LatLng(la,lon);
ter.add(latLngo);
weatherInfo.add(icon);
// Now initiate the next call
i++;
if(i < arrayLis.size()) getItAgain();
Log.i("LETSCHECK",""+la+", "+lon +icon);
}
public void getItAgain() {
// Just start the first task without the loop.
double latitude = ((LatLng)arrayLis.get(i)).latitude;
double longitude = ((LatLng)arrayLis.get(i)).longitude;
String url = "https://api.darksky.net/forecast/APIKEY/"
+latitude+","+longitude+"?units=si";
LatLng mylatlng = new LatLng(latitude,longitude);
startProcess(url,mylatlng);
}
Update
I do not understand why it is showing 2 instead of 4 results. However, I think you need to modify the code as follows.
private void getCurrentDetails(String data,LatLng myLatlng) throws JSONException {
JSONObject main = new JSONObject(data);
double la = main.getDouble("latitude");
double lon = main.getDouble("longitude");
JSONObject currently = main.getJSONObject("currently");
String summary = currently.getString("summary");
double temperature = currently.getDouble("temperature");
String icon = currently.getString("icon");
// Move this upto here
weatherInfo.add(icon);
loopi++;
Log.i("LETSCHECK",""+loopi);
if (loopi < arrayList.size()) {
getItAgain();
} else {
for (String l:weatherInfo) {
Log.i("LETSCHECK",l);
//expected 4 items to show but its showing only 2 items
}
}
}
You are running asynchronous code, and the reason that their callbacks are not returning one after the other is logical since each HTTP call takes some amount of time, unrelated to the others, so the response for your third call might actually be received before the first one.
You can either use RxJava (and its operators), or improve the current code. For your startProcess, pass the i from your for-loop as the index and create an array outside of our function. Whenever you get a response from the server, you can store it in the ith position of your array. After each call you can check If all values have been fetched (by a counter or something). It is much better to move all this stuff to its own class so its contained and testable.
One thing to note is how you are gonna handle errors, because your calls might not be received successfully.
This is a sample code. After each success call remove the current proceed item from the array and do a another API request.
private void startProcess(ArrayList<LatLong> elementList){
LatLong myLatlng = elementList.get(0);
OkHttpClient httpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(myurl)
.build();
Call call = httpClient.newCall(request);
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, Response response) throws IOException {
String data = response.body().string();
Log.i("DATASS",data);
if (response.isSuccessful()){
try {
getCurrentDetails(data,myLatlng);
elementList.remove(0)
startProcess(elementList)
}catch (JSONException e){
e.printStackTrace();
}
}
}
});
}
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)
I have ListView which have data . Data come from server and I want the ListView to update after every 5 sec. How to do this? I am new to android development. Please help me. Here is my code...
protected void showList() {
try {
JSONObject jsonObj = new JSONObject(myJSON);
peoples = jsonObj.getJSONArray(TAG_RESULTS);
for (int i = 0; i < peoples.length(); i++) {
JSONObject c = peoples.getJSONObject(i);
String data = c.getString(TAG_DATA);
final String dataaaa = rcdata.getText().toString().trim();
HashMap<String, String> user_data = new HashMap<String, String>();
user_data.put(TAG_DATA, data);
personList.add(user_data);
}
ListAdapter adapter = new SimpleAdapter(
DataSendActivity.this, personList, R.layout.layout_chat,
new String[]{TAG_DATA},
new int[]{R.id.data}
);
list.setAdapter(adapter);
} catch (JSONException e) {
e.printStackTrace();
}
}
Use a Handler and its postDelayed method to invalidate the list's adapter as follows:
final Handler handler = new Handler();
handler.postDelayed( new Runnable() {
#Override
public void run() {
adapter.notifyDataSetChanged();
handler.postDelayed( this, 5000 );
}
}, 5000 );
You must only update UI in the main (UI) thread.
you can see this question and comment How to refresh/Update Serialize Java Object in sharedPreferences. the user want the same. however it is good to use loader with service for that kind of problem calling asyncTask within minute is not good. also theres is a sample project you can check this https://github.com/Michenux/YourAppIdea also available in playstore which check for data changes from server.
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?