Cannot set text in TextView from a Async class [duplicate] - java

This question already has answers here:
Android "Only the original thread that created a view hierarchy can touch its views."
(33 answers)
Closed 4 years ago.
In my CountryInfoActivity.java I have an Async Class which retrieves a JSON from this website: https://pt.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&titles=Portugal.
It then parses the extract node into a String so I can set it in my TextView.
The problem is, whenever I set the text in the text view, my app crashes. The JSON parse is correct as it is retrieving all the information I want...
These are the classes I use to retrieve the data and, in the last one I try to set the data textoSobrePais into my TextView... By the way, in my onCreate method I called the class this way new DownloadTask().execute(url);
public class DownloadTask extends AsyncTask<String,Integer,Void>{
#Override
protected Void doInBackground(String... params) {
String url = params[0];
getJSONFromURL(url);
return null;
}
}
public String getJSONFromURL(String url){
String json_string = null;
try{
URL obj = new URL(url);
HttpURLConnection http = (HttpURLConnection) obj.openConnection();
http.setRequestMethod("GET");
int response = http.getResponseCode();
Log.i("response",Integer.toString(response));
BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine())!= null){
sb.append(line+"\n");
}
reader.close();
json_string = sb.toString();
Log.i("json_string",json_string);
} catch (UnsupportedEncodingException e){
e.printStackTrace();
} catch (ClientProtocolException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
ParseJson(json_string);
return null;
}
public void ParseJson (String json){
JSONObject obj = null;
try {
obj = new JSONObject(json);
} catch (JSONException e) {
e.printStackTrace();
}
try {
JSONArray pageIdObj = obj.getJSONObject("query").getJSONObject("pages").names();
String page = String.valueOf(pageIdObj.get(0));
Log.i("ASdasd",page);
textoSobrePais = obj.getJSONObject("query").getJSONObject("pages").getJSONObject(page).getString("extract");
page = "";
Log.i("texte",textoSobrePais);
txtInfoPais = findViewById(R.id.txtInfoPais);
txtInfoPais.setText(textoSobrePais);
} catch (JSONException e) {
e.printStackTrace();
}
}
This is the error that is giving me when it crashes:
https://pastebin.com/PJh5r36u
Can somebody help, please?

We can't update UI from Background Thread. You have to set text on main thread
Run on main thread like this
runOnUiThread(new Runnable() {
#Override
public void run() {
txtInfo.setText(textoPais);
}
});

You cannot update UI components from non-UI Thread. Run the update of TextView on UI thread as follows:
runOnUiThread(new Runnable() {
#Override
public void run() {
txtInfoPais.setText(textoSobrePais);
}
});

Related

How can I parse JSON with comma in android?

Im trying to parse JSON and my code is not working with one URL and it works with other. I think its the coma in question but I did not sure so please look at my code and help.
I tried to replace coma with dot in loop, but what i get is JSON Exception. And when I change the URL, there is no exception. Confusing...
This is not working URL: http://api.hnb.hr/tecajn/v1
And this is one I tested and it works: https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=e402c76fc8584a1c81849179f1277a74
While changed the URL I also changed the data, so its not the problem in writing..I guess
Here is my code for coma replacement:
private String convertStreamToString(InputStream is) {
BufferedReader reader = new BufferedReader(new
InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
try {
while ((line = reader.readLine()) != null) {
String coma = line.replaceFirst(",",".");
sb.append(coma).append('\n');
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
And the other part with URL and JSONArray:
#Override
protected Void doInBackground(Void... arg0) {
URLconnection urlConn = new URLconnection();
// Making a request to url and getting response
String url = "http://api.hnb.hr/tecajn/v1";
//.........connection.........
String response = urlConn.makeServiceCall(url);
Log.e(TAG, "Response from url: " + response);
if (response != null) {
try {
JSONObject jsonObj = new JSONObject(response);
// Getting JSON Array node
arr = jsonObj.getJSONArray("values");
for (int i = 0; i < article.length(); i++) {
JSONObject c = arr.getJSONObject(i);
header = c.getString("Valuta");
}
} catch (final JSONException e) {
Log.e(TAG, "Json parsing error.");
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(),
"Json parsing error: 216",
Toast.LENGTH_LONG).show();
}
});
}
Commas aren't the issue. It's the very first character. One is a square bracket and the other is a curly one.
You therefore need to parse the first response as an Array rather than an Object
new JSONArray(response);
That being said, you therefore cannot use the same methods for both URLs

Getting JSON Array from top/new category of Reddit in Java not working as expected

I am passing the url https://www.reddit.com/r/wallpapers/top/.json into my method for getting the JSON array of a subreddit. However, it only returns the JSON array for the hot category rather than the top or new categories. I have checked the URL and code thoroughly and have tried other different formats of the URL to only get the same results. For some reason all JSON gets all return only the hot page or default subreddit URL. But when I visit the URL in my browser that I've linked, it displays the correct JSON array for the top category. (Android Studio)
Here's the beginning of my JSON task that returns the array:
private class JsonTask extends AsyncTask<String, String, String> {
protected void onPreExecute() {
super.onPreExecute();
}
protected String doInBackground(String... params) {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL(params[0]);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
InputStream stream = null;
try {
stream = connection.getInputStream();
} catch (Exception e) {
Log.e("Subreddit Closed", urlString);
connection.disconnect();
return null; //if can't retrieve JSON file
}
reader = new BufferedReader(new InputStreamReader(stream));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
buffer.append(line + "\n");
}
return buffer.toString();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
finally {
if (connection != null) {
connection.disconnect();
}
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
Update: This was an issue with Reddit's API, it is now working as expected. Take caution of URL formats as */hot/.json is equivalent to */.json

Run code in AsyncTask stucking UI

I'm trying a copule of days to figure it out, without success,
Target: get use country by IP using asyncTask without stucking UI, and save it into string parm.
Problem: The UI doen't move until the asyncTask finish it job :/
MainActivity ,onCreate method:
.......
JSONObject jsonObject;
try {
jsonObject = new getJSONObjectFromURL(LoginActivity.this).execute("http://ip-api.com/json").get();
if (jsonObject != null) {
if (!jsonObject.getString("status").equals("success"))
throw new InterruptedException();
Log.d(TAG, "onCreate: jsonObject.tostring:" + jsonObject.toString());
countryLocation = jsonObject.getString("country");
Log.d(TAG, "onCreate: countryByIP " + countryLocation);
}
if (!isNetworkAvailable()/* || countryLocation == null*/)
Toast.makeText(MainActivity.this, "Please enable data.", Toast.LENGTH_SHORT).show();
//in button click redo all that code for no internet connection,.
} catch (InterruptedException | ExecutionException | JSONException e) {
e.printStackTrace();
}
asyncTask:
public class getJSONObjectFromURL extends AsyncTask<Object, Object, JSONObject> {
private Context context;
public getJSONObjectFromURL(Context context) {
this.context = context;
}
#Override
protected JSONObject doInBackground(Object... params) {
HttpURLConnection urlConnection = null;
URL url = null;
try {
url = new URL((String) params[0]);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(10000 /* milliseconds */);
urlConnection.setConnectTimeout(15000 /* milliseconds */);
urlConnection.setDoOutput(true);
urlConnection.connect();
BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
String jsonString;
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
jsonString = sb.toString();
return new JSONObject(jsonString);
} catch (JSONException | IOException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(JSONObject result) {
//Update the String..
}
}
Everything works perfect, i get the Json, able to read the Country from it, but MainActivity waiting for the onPostExecute to continue.
You are running your AsyncTask in a synchrounous state.
jsonObject = new getJSONObjectFromURL(LoginActivity.this).execute("http://ip-api.com/json").get();
By calling get() you are waiting until the Task has finished it's job. Do not use get() if you want to have a truly asynchronous process. You can use a callback to get notified.

Can't download html source from a web page

im having problems downloading html source from a web page in android. I run the http client in a different thread and it is able to get the html text ( i logged the result) but later when i try to work with the downloaded html text the variable seems to be empty from the main thread. I assume the problem is rising because im unable to synchronize threads but i don't know how to fix it for now. When i debug the code, the global variable contains data in the run function but when i join threads and look after the join method the variable is empty. Here is my code (class which i run in a different thread)
public class LutrijaHr {
public String url;
public String savedHtml;
public LutrijaHr(String s){
this.url = s;
savedHtml = "";
}
public String donwloadSource(String passedUrl) throws Exception{
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(passedUrl);
HttpResponse response = client.execute(request);
String html = "";
InputStream in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder str = new StringBuilder();
String line = null;
while((line = reader.readLine()) != null)
{
str.append(line);
}
in.close();
html = str.toString();
savedHtml += html;
return html;
}
}
Parts of code from the main class:
String test = "";
LutrijaHr lhr = new LutrijaHr("https://www.lutrija.hr");
#Override
public void run() {
try {
test = lhr.donwloadSource(lhr.url);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
lhr.savedHtml = test;
Log.d("test", test);
}
Here is the part where i try to join the threads but the variable is empty
if (v.getId() == R.id.checkNumber){
Thread t = new Thread(new LotoMain(), "Page thread");
t.start();
try {
t.join();
etCheckedNumber.setText(lhr.savedHtml);
String smrki = test;
Log.d("testdsadasd", lhr.savedHtml);
Log.d("BOZO BOZO" ,test) ;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
Log.d("BOZO BOZO BOZO" ,test) ;
e.printStackTrace();
}
}
I would like to solve this problem without using the android asynctask class because i want to learn a bit about threads and how they operate.
Use "Lock"
Add this code to main class:
public Lock workingLock = new ReentrantLock();
String test = "";
LutrijaHr lhr = new LutrijaHr("https://www.lutrija.hr");
#Override
public void run() {
try {
workingLock.lock();
test = lhr.donwloadSource(lhr.url);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
lhr.savedHtml = test;
workingLock.unlock;
Log.d("test", test);
}
Now use it in:
if (v.getId() == R.id.checkNumber){
Thread t = new Thread(new LotoMain(), "Page thread");
try {
try {
workingLock.lock();
} catch (Exception e) {
e.printStackTrace();
}
etCheckedNumber.setText(lhr.savedHtml);
String smrki = test;
Log.d("testdsadasd", lhr.savedHtml);
Log.d("BOZO BOZO" ,test) ;
workingLock.unlock();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
Log.d("BOZO BOZO BOZO" ,test) ;
e.printStackTrace();
}
}
Try this, it will return the source of a given page as one long string which you can then manipulate however you need, and as its a standalone class/method you can call it on the UI thread or asyc or however you choose to.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
public class WebPage {
public static String getWebSource(String Url) throws IOException {
URL url = new URL(Url);
URLConnection urlConnection = url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(
urlConnection.getInputStream(), "UTF-8"));
String inputLine;
StringBuilder sb = new StringBuilder();
while ((inputLine = br.readLine()) != null)
sb.append(inputLine);
br.close();
return sb.toString();
}
}
Edit: If you want to call it from the UI thread, android won't by default let you do that. you will need to change the apps thread policy which can by done by running this when the app starts (Required a min API of 9)
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
.permitAll().build();
StrictMode.setThreadPolicy(policy);
I "solved" the problem with declaring
String test = "";
as
static String test = "";
Even if this soultion works i don't understand why it wouldn't work with my original solution. If someone could light up this for me it would be really helpfull

Perform AsyncTask on Android / Posting JSON

I am working on an android app, and am running into some troubles with registering users. I want to post a JSON object to my server and receive one back. I can successfully create a JSON object with the right information but when I go to post it I get a NetworkOnMainThreadException or my HttpClient class returns null when it should be returning a JSONObject and I am very confident that my web server works correctly. I understand that you cannot connect to the network on the main thread and have created an HttpClient class that uses AsnycTask (although probably not correctly). I have been working on this for quite a while and would appreciate any guidance in the right direction.
//Main activity
#Override
public void onClick(View arg0) {
if(!(isEmpty(name) || isEmpty(username) || isEmpty(password) || isEmpty(email))) {
user = new JSONObject();
try {
user.put("username", username.getText().toString());
user.put("name", name.getText().toString());
user.put("email", email.getText().toString());
user.put("password", password.getText().toString());
} catch (JSONException e) {
e.printStackTrace();
}
jRegister = new JSONObject();
try {
jRegister.put("apiToken", Utilities.apiToken);
jRegister.put("user", user);
Log.i("MainActivity", jRegister.toString(2));
} catch (JSONException e) {
e.printStackTrace();
}
//
HttpClient client = new HttpClient(url, jRegister);
result = client.getJSONFromUrl();
try {
if(result != null)
tv.setText(result.toString(2));
else
tv.setText("null");
} catch (JSONException e) {
e.printStackTrace();
}
}else {
tv.setText("");
}
}
HttpClient Class
public class HttpClient extends AsyncTask<Void, Void, JSONObject>{
private final String TAG = "HttpClient";
private String URL;
private JSONObject jsonObjSend;
private JSONObject result = null;
public HttpClient(String URL, JSONObject jsonObjSend) {
this.URL = URL;
this.jsonObjSend = jsonObjSend;
}
public JSONObject getJSONFromUrl() {
this.execute();
return result;
}
#Override
protected JSONObject doInBackground(Void... params) {
try {
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpPost httpPostRequest = new HttpPost(URL);
StringEntity se;
se = new StringEntity(jsonObjSend.toString());
// Set HTTP parameters
httpPostRequest.setEntity(se);
httpPostRequest.setHeader("Accept", "application/json");
httpPostRequest.setHeader("Content-type", "application/json");
long t = System.currentTimeMillis();
HttpResponse response = (HttpResponse) httpclient.execute(httpPostRequest);
Log.i(TAG, "HTTPResponse received in [" + (System.currentTimeMillis()-t) + "ms]");
HttpEntity entity = response.getEntity();
if (entity != null) {
// Read the content stream
InputStream instream = entity.getContent();
// convert content stream to a String
String resultString= convertStreamToString(instream);
instream.close();
resultString = resultString.substring(1,resultString.length()-1); // remove wrapping "[" and "]"
JSONObject jsonObjRecv = new JSONObject(resultString);
// Raw DEBUG output of our received JSON object:
Log.i(TAG,"<JSONObject>\n"+jsonObjRecv.toString()+"\n</JSONObject>");
return jsonObjRecv;
}
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(JSONObject jObject) {
result = jObject;
}
private static String convertStreamToString(InputStream is) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
}
I understand that you cannot connect to the network on the main thread
and have created an HttpClient class that uses AsnycTask (although
probably not correctly).
You are right you have not implemented it the right way.
In your onClick events (still on Main thread) you performed a network activity causing the error:
HttpClient client = new HttpClient(url, jRegister);
result = client.getJSONFromUrl();
Instead you should run the network operation inside of the AsnycTask
public class GetJsonTask extends AsyncTask<Void, Void, JSONObject >{
private String URL;
private JSONObject jsonObjSend;
public GetJsonTask(String URL, JSONObject jsonObjSend) {
this.URL = URL;
this.jsonObjSend = jsonObjSend;
}
#Override
protected JSONObject doInBackground(Void... params) {
JSONObject jsonObjRecv;
try {
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpPost httpPostRequest = new HttpPost(URL);
StringEntity se;
se = new StringEntity(jsonObjSend.toString());
// Set HTTP parameters
httpPostRequest.setEntity(se);
httpPostRequest.setHeader("Accept", "application/json");
httpPostRequest.setHeader("Content-type", "application/json");
long t = System.currentTimeMillis();
HttpResponse response = (HttpResponse) httpclient.execute(httpPostRequest);
Log.i(TAG, "HTTPResponse received in [" + (System.currentTimeMillis()-t) + "ms]");
HttpEntity entity = response.getEntity();
if (entity != null) {
// Read the content stream
InputStream instream = entity.getContent();
// convert content stream to a String
String resultString= convertStreamToString(instream);
instream.close();
resultString = resultString.substring(1,resultString.length()-1); // remove wrapping "[" and "]"
jsonObjRecv = new JSONObject(resultString);
// Raw DEBUG output of our received JSON object:
Log.i(TAG,"<JSONObject>\n"+jsonObjRecv.toString()+"\n</JSONObject>");
}
}
catch (Exception e) {
e.printStackTrace();
}
return jsonObjRecv;
}
protected void onPostExecute(JSONObject result) {
try {
if(result != null)
tv.setText(result.toString(2));
else
tv.setText("null");
} catch (JSONException e) {
e.printStackTrace();
}
}else {
tv.setText("");
}
}
}
Then you call your async in onclik method like this:
public void onClick(View arg0) {
//.......
GetJsonTask client = new GetJsonTask(url, jRegister);
client.execute();
}
One problem in your code is that your expectations of AsyncTask aren't quite right. In particular this function:
public JSONObject getJSONFromUrl() {
this.execute();
return result;
}
AsyncTask runs the code in the doInBackground() function in a separate thread. This means that once you call execute() you have two parallel lines of execution. You end up with what's called a Race Condition. When you reach the return result line, a couple of things can be happening:
doInBackground() hasn't run and therefore result is still has the default value. In this case null.
doInBackground() can be in the middle of the code. In your particular case because it doesn't modify result then this doesn't affect you much. But it could be on any line (or middle of a line sometimes if operations aren't atomic) when that return happens.
doInBackground() could've finished, but since onPostExecute() runs on the UI thread it has to wait until your onClick handler is finished. By the time onPostExecute() has a chance to run onClick already tried to update tv with whatever it was that getJSONFromUrl returned, most likely null.
The way to set up tasks with AsyncTask is to give it the information it needs to do it's work, start it up with execute, and since you can't know how long it will take to complete, let it handle the finishing steps of the task.
This means that after calling execute you don't wait around for it's result to update views (like in your case), but rather rely on the AsyncTask's onPostExecute or related methods to take over the next steps.
For your case this would mean that your onPostExecute should look something like:
protected void onPostExecute(JSONObject result) {
try {
if(result != null)
tv.setText(result.toString(2));
else
tv.setText("null");
} catch (JSONException e) {
e.printStackTrace();
}
}

Categories