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();
}
}
}
});
}
Related
I have been struggling all day with this seemlesly unimportant issue, but I need to get it sorted. I will post all my code and explain it. I need to parse a CSV into a HashMap with structure {key, value} where value is an ArrayList of ArrayLists of Strings.
HashMap<String, ArrayList<ArrayList<String>>>
In the back, using AsyncTask, I get the data correctly into the map using a CSV document where I have the information stored. Here is my AsyncTask, just in case, but I can 99% say it works just fine.
public class ReadAllPaths extends AsyncTask<Integer, Double, HashMap<String, ArrayList<ArrayList<String>>>> {
public interface AsyncResponse {
void processFinish(HashMap<String, ArrayList<ArrayList<String>>> output);
}
public static final String TAG = "ReadAllPaths";
public AsyncResponse delegate;
#SuppressLint("StaticFieldLeak")
private final Context context;
public ReadAllPaths(Context context, AsyncResponse delegate) {
this.context = context;
this.delegate = delegate;
}
#Override
protected HashMap<String, ArrayList<ArrayList<String>>> doInBackground(Integer... allPaths) {
return readData(allPaths[0]);
}
#Override
protected void onProgressUpdate(Double... progress) {
DecimalFormat df = new DecimalFormat();
df.setMaximumFractionDigits(2);
}
#Override
protected void onPostExecute(HashMap<String, ArrayList<ArrayList<String>>> result) {
delegate.processFinish(result);
}
private HashMap<String, ArrayList<ArrayList<String>>> readData(int allPaths) {
InputStream is = context.getResources().openRawResource(allPaths);
BufferedReader reader = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8));
String line = "";
HashMap<String, ArrayList<ArrayList<String>>> all_paths = new HashMap<>();
ArrayList<ArrayList<String>> value_date;
try {
while ((line = reader.readLine()) != null) {
if (isCancelled()) break;
value_date = new ArrayList<>();
// Split the line into different tokens
String[] tokens = line.split(";");
String key_date = tokens[0];
value_date.add(new ArrayList<>(Arrays.asList(tokens[1].split("\\s*,\\s*"))));
value_date.add(new ArrayList<>(Arrays.asList(tokens[2].split("\\s*,\\s*"))));
value_date.add(new ArrayList<>(Arrays.asList(tokens[3].split("\\s*,\\s*"))));
all_paths.put(key_date, value_date);
}
} catch (IOException e) {
Log.d(TAG, "Error " + line, e);
e.printStackTrace();
return null;
}
return all_paths;
}
}
In my main Activity I make a call to it and its onPostExecute method. Again, posting it just in case,
ReadAllPaths readAllPaths = (ReadAllPaths) new ReadAllPaths(this, output -> {
// Here you will receive the result fired from async class
// of onPostExecute(result) method.
Log.d(TAG, "processFinish: Process finished!");
everyPath = output;
Log.d(TAG, "onCreate: output: " + output);
}).execute(R.raw.all_paths);
What is strange is wat happens now. I can make a log.d, or print or whatever and print my output, and it will print my data perfectly. On the next stage, I need to "dynamically" access the map, so I use this simple function (level is just a avlue from 0 to 2):
private ArrayList<String> getPath(String date, int level) {
// Example of structure
// {"19-05-2022": [[path1], [path2], [path3]}]}
return everyPath.get(date).get(level);
}
Now this works perfeclty everytime except for the first element in my CSV. I know maps don't have an order, so I tried changing my CSV and the exception keeps occurring on the first line of my CSV though it is correctly displayed when I do my log.d. Also I have tried checking if key exists via:
everyPath.containsKey(date);
And also returns false only on the first row of my CSV. At first I thought this could be due to dealing with headers in the CSV, but again, the data is correctly displayed when I print the output of my AsyncTask. Only the part where I need to fetch the data is not working.
Also I found a way around, which is duplicating my first row of the CSV, but I would rather understand what is going on here.
Thank you!
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 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!
Regarding
I have a listview contains 4 items 3 of them are texts and fourth is image
all data are on server within json
the code work correctly but when the internet is off all items and list does not appear how can make the list works with and without internet connection
because I add everyday new items to database
and this my code
requestQueue = Volley.newRequestQueue(this);
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, url,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
try {
JSONArray jsonArray = response.getJSONArray("allstudents");
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject respons = jsonArray.getJSONObject(i);
String id = respons.getString("id");
String name = respons.getString("name");
String info = respons.getString("info");
String img = respons.getString("img");
link = respons.getString("link");
voicelink = respons.getString("voicelink");
listitmes.add(new listitme(id, name, info, img, link, voicelink));
allitems();
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("VOLLEY", "ERROR");
}
}
);
requestQueue.add(jsonObjectRequest);
}
public void allitems() {
listAdapter lsadapter = new listAdapter(listitmes);
listView.setAdapter(lsadapter);
}
any solution please I searched a lot but no any answer
and I did not find any thing like here on StackOverflow.Com
Look at https://developer.android.com/reference/android/content/SharedPreferences.
With Shared SharedPreferences you can save the list and call it if there is no wifi!
what you can do is:
Save the data that you download, to a file (image)/properties (text) as soon as you receive data from Internet. ()
Display a message in your activity when the data was fetched (e.g. Last Synced : Timestamp).
If you can can't connect to Internet to get the new data, load the data from file/properties.
If you can't connect to Internet and don't have any saved data, display a message (e.g. can't connect to Internet - may be color it to highlight it's an error).
my 2 cents...
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?