Currently in my application I execute a JSONDownloader, then tell my application to wait for a set period of time before updating fields with the retrieved data. How do I go about making the application wait for the JSONDownloader to finish its' task before I update the fields?
The JSONDowloader class is written (abridged) as follows and is run as an asynchronous activity. This class is called when needed and sets global variables which, after a set amount of time after being called are updated. I'd like the fields to be updated as soon as the class has finished running.
public class JSONDownloader extends AsyncTask<Object, Object, Object>{
#Override
protected Object doInBackground(Object... params) {
if(JSONstate == false){
try {
final URL url = new URL("https://irrelevant");
final URLConnection urlConnection = url.openConnection();
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
urlConnection.connect();
final InputStream inputStream = urlConnection.getInputStream();
final StringBuilder sb = new StringBuilder();
while (inputStream.available() > 0) {
sb.append((char) inputStream.read());
}
System.out.println("up to setting string");
String result = sb.toString();
JSONObject jsonOrg = new JSONObject(result);
String ok = "ok";
Response = jsonOrg.getString("response");
System.out.println(Response);
if(Response.equals(ok)){
Settingsresponse = true;
orgName = jsonOrg.getString("orgName");
System.out.println("orgName" + orgName);
accessPointName = jsonOrg.getString("accessPointName");
System.out.println("accessPointName" + accessPointName);
lat = jsonOrg.getString("latitude");
System.out.println("lat" + lat);
longi = jsonOrg.getString("longitude");
System.out.println("longi" + longi);
floor = jsonOrg.getString("floor");
System.out.println("floor" + floor);
orgId = jsonOrg.getString("orgId");
System.out.println("orgId" + orgId);
}
else{
System.out.println("Data sent was erroneous");
Settingsresponse = false;
}
} catch (Exception e) {
System.err.print(e);
}
}
return null;
}
protected void onPostExecute(Void result){
}
}
The JSONDownloader is called within whatever method it is needed and then immediately after this is called; currently a set time is used before updating fields as thus:
public void waitthencall()
{
long t0,t1;
t0=System.currentTimeMillis();
do{
t1=System.currentTimeMillis();
}
while (t1-t0<2000);
setfields();
}
you need a Callback listener to update your UI. Example
Create a callback listener and implement it inside your activity or from where you are updating the UI.
By using asynchronous task!
http://www.vogella.com/articles/AndroidBackgroundProcessing/article.html
May help!
Dude, the method onPostExecute is call once doInBackGround has completed processing call your method from there, it will assure you that doInBackGround has finished its execution
Related
This question already has answers here:
Android 8: Cleartext HTTP traffic not permitted
(37 answers)
Closed 2 years ago.
I'm building an application on Android Studio that retrieves weather information in real time thanks to OpenWeatherMap and the API they offer.
The problem is, I'm using two phones with different SDKs. One is SDK 23 / Android 6.0 and the other is SDK 28 / Android 9.0.
Currently on the phone with SDK 23 I have no problem. However on the phone with SDK 28 I have a NullPointerException error. My second activity allows me to display information for city X and its weather information. So, finally the error I'm encountering on the phone with SDK 28 is this one :
java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
I've looked into a lot of things to see where that could have come from, if it wasn't my AsyncTask or whatever, but I really don't see it.
Knowing that on the phone with the oldest version of Android it retrieves well the information from my editText that on the most recent version it doesn't retrieve it at all and the nullpointerException must come from there.
Do you know where this might be coming from?
Here is my AsyncTask :
public class ExecuteTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... strings) {
HttpURLConnection con = null ;
InputStream is = null;
try {
con = (HttpURLConnection) ( new URL(strings[0])).openConnection();
con.setRequestMethod("GET");
con.setDoInput(true);
con.setDoOutput(true);
con.connect();
// Let's read the response
StringBuffer buffer = new StringBuffer();
is = con.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line = null;
while ( (line = br.readLine()) != null )
buffer.append(line + "\r\n");
is.close();
con.disconnect();
return buffer.toString();
}
catch(Throwable t) {
t.printStackTrace();
}
finally {
try { is.close(); } catch(Throwable t) {}
try { con.disconnect(); } catch(Throwable t) {}
}
return null;
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
try {
String message = "";
String degre="";
String idMeteo="";
JSONObject jsonObject = new JSONObject(s);
String infoWeatherToday = jsonObject.getString("weather");
JSONObject WeatherTemperature = jsonObject.getJSONObject("main");
Integer deg = WeatherTemperature.getInt("temp");
deg = deg - 273;
JSONArray array = new JSONArray(infoWeatherToday);
int tablongueur=array.length();
for (int i = 0; i < tablongueur; i++) {
JSONObject jsonSecondary = array.getJSONObject(i);
String main = "";
//Integer id;
main = jsonSecondary.getString("main");
// id = jsonSecondary.getInt("id");
switch (main) {
case "Clouds":
main = "Nuageux";
PhotoMeteo.setImageResource(R.drawable.cloud);
break;
case "Clear":
main = "Ensoleillé";
PhotoMeteo.setImageResource(R.drawable.sun);
break;
case "Rain":
main = "Pluie";
PhotoMeteo.setImageResource(R.drawable.rain);
break;
case "Snow":
main = "Neige";
PhotoMeteo.setImageResource(R.drawable.snow);
break;
case "Smoke":
main = "Brouillard";
PhotoMeteo.setImageResource(R.drawable.smoke);
break;
case "Drizzle":
main = "Brumeux";
PhotoMeteo.setImageResource(R.drawable.drizzle);
break;
default:
main = "Météo introuvable !";
PhotoMeteo.setImageResource(R.drawable.ic_warning);
}
if (main != "" /*&& id != null*/) {
message += main + "\r\n";
degre += deg + "°C";
//idMeteo += "L'id de la météo est" + id;
}
}
if (message != "") {
resultWeather.setText(message);
resultDegre.setText(degre);
//resultIdMeteo.setText(idMeteo);
} else {
Toast.makeText(AccueilActivity.this, "Une erreur a eu lieu ", Toast.LENGTH_SHORT).show();
}
} catch (JSONException e) {
e.printStackTrace();
}
}
And here is the intent that I keep from my first activity called RegisterActivity to give it as a parameter for the "name" of the city
Intent intent = new Intent(RegisterActivity.this, AccueilActivity.class);
intent.putExtra(EXTRA_TEXT,cityField.getText().toString());
startActivity(intent);
In my 2nd activity called "AccueilActivity"
Intent intent = getIntent();
if(intent!=null)
{
textViewVille.setText(intent.getStringExtra(EXTRA_TEXT));
ville = intent.getStringExtra(EXTRA_TEXT);
FindWeather();
}
And my final function called FindWeather which execute the AsyncTask
public void FindWeather() {
cityToFind = ville;
try {
ExecuteTask tasky = new ExecuteTask();
tasky.execute("http://api.openweathermap.org/data/2.5/weather?q=" + cityToFind + "&APPID={MYAPKKEY}&LANG=fr&UNITS=metric");
} catch (Exception e) {
e.printStackTrace();
}
}
Just I don't give you the value of my APK Key because it isn't something interesting but the value is present in the initial code.
If I have a last things to add, ville is a simple TextView and cityToFind the value of my editText on the first activity.
If you need anything of my source code I can give you more.
Thank you.
doInBackground is going to return null if there is any exception in your HTTP code.
That is passed to onPostExecute as the parameter.
You then try to constuct a JSONObject(null), which is an invalid argument
All in all, please pick a higher level HTTP library with fault tolerance built in
Comparison of Android networking libraries: OkHTTP, Retrofit, and Volley
I also suggest writing unit tests outside of that class and running them from the IDE rather than a device, so you verify the network calls actually work.
I'm working on an Android app that is going to call the DarkSky weather API (I have redacted my API key here for obvious reasons). My problem comes when I parse the JSON data and push it to a stack I named dataStack. At the time of pushing the stack I log its size and it shows correctly. However when my code reaches the buildGraph() method, the stack is now empty and all my data has disappeared. What causes the stack to empty?
EDIT: As of 30 minutes after posting I found a workaround. I am now returning the String and parsing it in my MainActivity Android class. However, I still do not know why the stack was being deleted. I would love to know :)
public class MainActivity extends AppCompatActivity {
Button button;
TextView progressLabel;
GraphView graph;
JSONObject jsonObject;
static Stack<DataPoint> dataStack = new Stack<>(); // stack for graph data points
static final String API_URL = "https://api.darksky.net/forecast/API_KEY/42.3611,-71.0570,"; // #TODO: delete API key before comitting to GitHub
static final String URL_TAIL = "?exclude=currently,flags,hourly"; // end of URL
static final long currTime = System.currentTimeMillis() / 1000L; // current UNIX time
static long unixTime = currTime;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
progressLabel = findViewById(R.id.progressLabel);
graph = findViewById(R.id.graph);
}
public void loadResults(View view) {
for (int x = 0; x < 2; x++) { // 7 API calls for each of 7 days
new APICall().execute();
unixTime -= 86400; // subtract 24 hours in UNIX time
dataStack.size();
}
buildGraph(); // after all data is gathered, build a graph using it
}
private void buildGraph() {
// #TODO: Method to build graph
Log.i("STACK pop", String.valueOf(dataStack.size()));
}
class APICall extends AsyncTask<Void, Void, String> { // Extend AsyncTask so we don't hijack the main UI thread
protected void onPreExecute() {
// Do stuff before executing the AsyncTask
progressLabel.setText("Fetching Data");
}
protected String doInBackground(Void... urls) {
// Execute background task here
try {
final String FULL_URL = API_URL + unixTime + URL_TAIL; // build the full URL with latest time
URL url = new URL(FULL_URL); // URL for the API call
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); // connection to URL
try {
// tools for reading API results
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
// accumulate results
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
bufferedReader.close(); // always close buffered reader
return stringBuilder.toString(); // return results
}
finally {
// inside a finally block so that no matter what we always end a connection that has been started
urlConnection.disconnect(); // end the connection
}
}
catch(Exception ex) {
Log.e("ERROR", ex.getMessage(), ex);
return null;
}
}
protected void onPostExecute(String response) {
// Do stuff after we're finished executing
if (response == null) {
response = "AN ERROR HAS OCCURRED";
}
else {
try {
jsonObject = new JSONObject(response); // create object from our response
JSONArray arr = jsonObject.getJSONObject("daily").getJSONArray("data"); // get data Array
String arrString = arr.getString(0); // full String
String[] splitString = arrString.split(","); // split String into array by comma
String time = splitString[0].substring(8); // time is the first index of the array, use substring to cutout unecessary info
String temp = splitString[11].substring(18);
dataStack.push(new DataPoint(Integer.valueOf(time), Float.valueOf(temp))); // push our data onto the stack as a DataPoint
Log.i("STACK push", String.valueOf(dataStack.toString()));
response = "Data received"; // display this to user
}
catch(Exception ex) {
response = "ERROR DURING JSON PARSING";
}
}
progressLabel.setText(response);
// parse data here
Log.i("INFO", response);
}
}
}
The stack is empty because result isn't in yet. The issue is with your loadResults().
public void loadResults(View view) {
for (int x = 0; x < 2; x++) { // 7 API calls for each of 7 days
new APICall().execute();
unixTime -= 86400; // subtract 24 hours in UNIX time
dataStack.size();
}
buildGraph(); // after all data is gathered, build a graph using it
}
You issued the new APICall().execute(); to request data and update the dataStack and you expect to get the dataStack results 'immediately' inside the same function loadResults()? It's not possible.
One solution is to remove the buildGraph() in loadResults() to inside onPostExecute().
So I just spent 6 hours worth of work, figuring out this 'little' fact:
Without calling client.getResponseCode() THE POST REQUEST DOES NOT GO THROUGH.
I hope that someone can explain why!
Specifically, for this minimalistic android client standalone code literally nothing happens without the line int status = client.getResponseCode();
but with it,everything works like magic.
I didn't find any official documentation about this, so I'm wondering what's up, or what I don't understand (The people implementing Java usually do outstanding job, so it's probably me not getting something :) ).
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final String theUrl = "http://xx.yyy.zz.aa:bb/securitiesFollowServer/users";
final String TAG = getClass().getSimpleName();
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
String body = "BODY OF MESSAGE";
HttpURLConnection client = null;
BufferedWriter outputPost = null;
try {
URL url = new URL(theUrl);
// open the connection
client = (HttpURLConnection) url.openConnection();
client.setRequestProperty("content-type", "text/plain");
//client.setRequestMethod("POST"); Someone claimed that setDoOutput(true) works so I will try that instead (I tried both,mutually and exclusively so all 3 options).
client.setDoOutput(true);
outputPost = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
outputPost.write(body);
outputPost.flush();
outputPost.close();
int status = client.getResponseCode();
StackTraceElement[] r = Thread.currentThread().getStackTrace();
String toNotepad = "";
for (int i = 0; i < r.length; ++i) {
toNotepad += '\n' + String.valueOf(i) + '.' + ':' + r[i].toString();
}
// This is where I set a breakpoint and got the stacktrace value from toNotepad,and copy pasted it.
} catch (IOException e) {
Log.e(TAG, "Error ", e);
} finally {
if (client != null) {
client.disconnect();
}
if (outputPost != null) {
try {
outputPost.close();
} catch (IOException e) {
Log.e(TAG, "IO ERROR");
}
}
return null;
}
}
};
task.execute();
}
}
For completeness of the question, here is the "server side" minimalistic code
#POST
#Consumes(MediaType.TEXT_PLAIN)
#Produces(MediaType.TEXT_PLAIN)
public String setDebugResource(String a){
return "I got this string:"+a;}
And here is the stacktrace (I made it obvious in the code above where exactly I copy-pasted its value):
When not working(or working, it is exactly the same stacktrace).:
0.:dalvik.system.VMStack.getThreadStackTrace(Native Method)
1.:java.lang.Thread.getStackTrace(Thread.java:580)
2.:dor.only.dorking.android.apppostrequest.MainActivity$1.doInBackground(MainActivity.java:52)
3.:dor.only.dorking.android.apppostrequest.MainActivity$1.doInBackground(MainActivity.java:28)
4.:android.os.AsyncTask$2.call(AsyncTask.java:292)
5.:java.util.concurrent.FutureTask.run(FutureTask.java:237)
6.:android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
7.:java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
8.:java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
9.:java.lang.Thread.run(Thread.java:818)
According to the HttpUrlConnection docs you must call setDoOutput(true) on the client object instead of setting the method to POST. The method will be set automatically to POST through this. They have an example under section "Posting Content".
There's an example here as well and a larger discussion here
Frankly, I would skip using the raw HttpUrlConnection class and use something like Volley.
I'm using Volley however I'm having some problems with the JSON parsed data most likely because volley doesn't implement something like AsyncTask's onPostExecute() and I'm getting some duplicated data on wrong list items.
Then I came across this: https://github.com/yakivmospan/volley-request-manager#custom-listener-implementation-
Has anyone use it? How can I add it to my current Volley code?
More details about my problem here Volley not sending correct data. How to implement an alternative to onPostExecute()?
UPDATE
As requested, some code. Here's a button that calls a method on another class that uses Volley to request some raw JSON data (NovaJSON) and then send the JSON to a parser class (NovaParser):
info.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String instanceDetail = NovaJSON.shared().receiveDetail(getId());
Dialog dialog = new Dialog(v.getContext());
dialog.setContentView(R.layout.instances_info);
TextView image = (TextView) dialog.findViewById(R.id.imageInstance);
TextView flavor = (TextView) dialog.findViewById(R.id.flavorInstance);
dialog.setTitle(name.getText() + " Details");
if (instanceDetail != null) {
image.setText(" \u2022 image : " + NovaParser.shared().parseImages(instanceDetail));
flavor.setText(" \u2022 flavor : " + NovaParser.shared().parseFlavor(instanceDetail));
}
dialog.show();
}
});
This is the method that does the Volley request on the NovaJSON class:
public void getJSONdetail() {
final String authToken = getAuth();
String novaURL = getNova();
novaURL = novaURL+"/servers/"+id;
JsonObjectRequest getRequest = new JsonObjectRequest(Request.Method.GET, novaURL, null,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
Log.d("Nova on Response", response.toString());
setNovaJSONdetail(response.toString());
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d("Nova on Error", "Error: " + error.getMessage());
setNovaJSONdetail(error.toString());
}
}
) {
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> params = new HashMap<String, String>();
params.put("X-Auth-Token", authToken);
params.put("User-Agent", "stackerz");
params.put("Accept", "application/json");
params.put("Content-Type", "application/json; charset=utf-8");
return params;
}
};
queue = VolleySingleton.getInstance(this).getRequestQueue();
queue.add(getRequest);
}
It then sends the JSON from the server as a string to be parsed using the following methods:
public static String parseImages(String imagesDetail){
ArrayList<HashMap<String, String>> imagesList = NovaParser.shared().getImagesList();
String temp = null;
JSONObject novaDetail = null;
try {
novaDetail = new JSONObject(imagesDetail);
JSONObject server = novaDetail.getJSONObject("server");
JSONObject image = server.getJSONObject("image");
if (imagesList !=null){
temp = image.getString("id");
for (Map<String,String> map : imagesList) {
if (map.containsValue(temp)) {
temp = map.get(NAME);
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return temp;
}
public static String parseFlavor(String instanceDetail){
ArrayList<HashMap<String, String>> flavorList = NovaParser.shared().getFlavorList();
String temp = null;
JSONObject novaDetail = null;
try {
novaDetail = new JSONObject(instanceDetail);
JSONObject server = novaDetail.getJSONObject("server");
JSONObject flavor = server.getJSONObject("flavor");
if (flavorList !=null){
temp = flavor.getString("id");
for (Map<String,String> map : flavorList) {
if (map.containsValue(temp)) {
temp = map.get(NAME);
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return temp;
}
When I press the button once the dialog is displayed with empty values. When I press it the second time I get the correct parsed data. Basically first time I click the button the instanceDetail string is null because Volley didn't finish doing its thing then I click the 2nd time it loads the values accordingly because it finally finished the 1st request.
I understand Volley is asynchronous, the requests happen in parallel and the responses sometimes are not immediate however I need some sort of progress bar or spinning wheel to give the user some feedback that the app is waiting for data. It could be done with AsyncTask however it doesn't seem to be possible with Volley.
I think your problem is not because of Volley.
Check the parameters you send and receive.
However if you need onPostExcecute you have Volley's callback:
Response.Listener<JSONObject> and Response.ErrorListener() which are called after the request.
About Volley request manager just switch all your volley calls with appropriate Volley request manager calls
I solved my problem by dumping Volley altogether and moving to Retrofit. I setup all the calls to be sync/blocking, worked out the exceptions/errors using try/catches and setup a short timeout on the OkHTTP client. Now it's working as I wanted.
I'm just learning about AsyncTask and want to use it as a separate class, rather then a subclass.
For example,
class inetloader extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... urls) {
String response = "";
DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(urls[0]);
try {
HttpResponse execute = client.execute(httpGet);
InputStream content = execute.getEntity().getContent();
BufferedReader buffer = new BufferedReader(
new InputStreamReader(content));
String s = "";
while ((s = buffer.readLine()) != null) {
response += s;
}
} catch (Exception e) {
e.printStackTrace();
}
return response;
}
#Override
protected void onPostExecute(String result) {
Log.e("xx",result);
// how do I pass this result back to the thread, that created me?
}
}
and the main(ui) thread:
inetloader il = new inetloader();
il.execute("http://www.google.com");
//il.onResult()
//{
///do something...
//}
Thanks!
Use a interface. Something like:
interface CallBackListener{
public void callback();
}
Then do this in your UI thread:
inetloader il = new inetloader();
li.setListener(this);
il.execute("http://www.google.com");
In inetloader, add:
CallBackListener mListener;
public void setListener(CallBackListener listener){
mListener = listener;
}
then In postExecute(), do:
mListener.callback();
you can pass the activity instance to constructor and call activity function from there...
Like use interface :
public interface ResultUpdatable {
public void setResult(Object obj);
}
Implement this in the Activity and pass in the constructor of Async task and update the result from onPostExecute using setResult function.
inetloader il = new inetloader();
il.execute("http://www.google.com");
String result = il.get();//put it in try-catch
^^^^^^^^
here you get result which is in onPostExecute(String result)