I'm doing and app who calls the same method two times and the second time I call the method it doesn't execute the line with HttResponse.
Here is the AsyncTask where everything starts:
private class FetchSubjectsTask extends AsyncTask<String, Void, List<Subject>> {
private ProgressDialog pd;
#Override
protected List<Subject> doInBackground(String... params) {
List<Subject> subjects = null;
try {
subjects = api.getMySubjects(params[0]);
} catch (Exception e) {
e.printStackTrace();
}
return subjects;
}
#Override
protected void onPostExecute(List<Subject> result) {
printSubjects(result);
if (pd != null) {
pd.dismiss();
}
}
}
Then I get the subjects through the method getMySubjects which is:
public List<Subject> getMySubjects(String username) {
List<Subject> subjects = new ArrayList<Subject>();
java.lang.reflect.Type arrayListType = new TypeToken<ArrayList<Subject>>() {
}.getType();
String url = BASE_URL_VM + "users/" + username + "/subjects";
HttpClient httpClient = WebServiceUtils.getHttpClient();
try {
System.out.println("inside try");
HttpResponse response = httpClient.execute(new HttpGet(url));
System.out.println("response executed");
HttpEntity entity = response.getEntity();
Reader reader = new InputStreamReader(entity.getContent());
subjects = gson.fromJson(reader, arrayListType);
} catch (Exception e) {
}
System.out.println("Array lenght " +subjects.size());
return subjects;
}
That's the first time HttpResponse executes and I get the "response executed" and "Array lenght 2" which are the number of subjects that user (username) has in the database.
The problem is when in onPostExecute I call the method printSubjects which is:
private void printSubjects(List<Subject> subjects){
adapter = new SubjectAdapter(this,(ArrayList<Subject>)subjects, (String) getIntent().getExtras().get("username"));
setListAdapter(adapter);
adapter.notifyDataSetChanged();
}
This calls the SubjectAdapter which prints the Subjects in a ListView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder viewHolder = null;
if (convertView == null) {
convertView = inflater.inflate(R.layout.subject_detail, null);
viewHolder = new ViewHolder();
viewHolder.tvsubject = (TextView) convertView
.findViewById(R.id.tvsubject);
boolean match = api.checkMySubjects(username, data.get(position).getId());
Button addButton = (Button) convertView.findViewById(R.id.bAdd);
if(match){
addButton.setText("Delete");
}else{
addButton.setText("Add");
}
String content = data.get(position).getName();
viewHolder.tvsubject.setText(content);
}
return convertView;
}
private static class ViewHolder {
TextView tvsubject;
TextView tvauthor;
}
There I call the method checkMySubjects which calls again getMySubjects to compare.
public boolean checkMySubjects(String username, String id) {
List<Subject> mySubjects = getMySubjects(username);
boolean match = false;
for (Subject s1 : mySubjects) {
if (s1.getId().equals(id)) {
match = true;
}
}
return match;
}
But now the method getMySubjects doesn't arrive until "Response executed" and the Array lenght is 0. It only shows "Inside try".
Why? I'm calling the same method with the same URL but first time I get the 2 subjects in the array and the second time I get nothing because HttResponse doesn't execute.
Thank you!
It's hard to say what's happening, but I'd like to point out what I think it's wrong. First, let's state the fact thatgetMySubjects performs a network operation, now, the first time you call it, it is called from within an AsyncTask on a thread other than the UI thread. This is perfectly fine
Then you call printSubjects in onPostExecute which populates a ListView with the results...
private void printSubjects(List<Subject> subjects){
adapter = new SubjectAdapter(this,(ArrayList<Subject>)subjects, (String) getIntent().getExtras().get("username"));
setListAdapter(adapter);
adapter.notifyDataSetChanged();
}
Now, the problem is in your getView implementation of the adapter. More specifically here...
boolean match = api.checkMySubjects(username, data.get(position).getId());
this is terribly bad because you are making a network connection on the UI thread everytime you inflate a ListView item, so if you had ten items you will be making a network connection ten times (plus everytime you you scroll up and/or down the list).
Moreover, Android complains with a FATAL exception if you attempt to make a network connection on the UI thread. You're not even noticing because you are not doing anything with this exception...
try {
System.out.println("inside try");
HttpResponse response = httpClient.execute(new HttpGet(url));
System.out.println("response executed");
HttpEntity entity = response.getEntity();
Reader reader = new InputStreamReader(entity.getContent());
subjects = gson.fromJson(reader, arrayListType);
} catch (Exception e) {
}
the catch block is empty, if you print it to the LogCat you'll see the details
Suggestion
Don't make a network connection on the UI thread...you can't
On getView instead of making a network connection for every single iteration, eagerly load the data in memory before you set the ListView
Again, the offending line of code is...
boolean match = api.checkMySubjects(username, data.get(position).getId());
in getView
Related
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 7 years ago.
I have been playing around with some JSON, trying to get and display the strings to show in a custom ListView with Textviews in my app. But I'm getting an NullPointerException when running. Can somebody tell me where I'm making a mistake and point me to a right direction maybe.
This is the JSON:
[{"id":"1","client_id":"1","client_name":"Company A"},{"id":"2","client_id":"2","client_name":"Company B"}]
this is the Model class:
public class Model {
private String client_name;
public String getClient_name() {
return client_name;
}
public void setClient_name(String client_name) {
this.client_name = client_name;
}
}
Wrap class:
public class Wrap {
private ArrayList<Model> models;
public ArrayList<Model> getModels() {
return models;
}
public void setModels(ArrayList<Model> models) {
this.models = models;
}
}
this is my Adapter:
public class Adapter extends ArrayAdapter<Model> {
public Adapter(Context context, int resource) {
super(context, resource);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.listview_item, null);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
final Model p = getItem(position);
viewHolder.textone.setText(p.getClient_name());
return convertView;
}
}
The ViewHolder :
public class ViewHolder {
public TextView textone;
public ViewHolder(View convertView)
{
textone = (TextView)convertView.findViewById(R.id.txt_field);
}
}
The url:
private static final String SO_URL = "http://www.example.com/webservice/?value";
private static final String PARAMS = "[{\"table\":\"locations\",\"operation\":\"select\"}]";
This is the AsyncTask's doInBackground method :
protected String doInBackground(Void... params) {
try {
//Create an HTTP client
String URL = URLEncoder.encode(PARAMS, "UTF-8");
HttpClient client = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(SO_URL + URL);
//Perform the request and check the status code
HttpResponse response = client.execute(httpPost);
StatusLine statusLine = response.getStatusLine();
if(statusLine.getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
InputStream content = entity.getContent();
try {
//Read the server response and attempt to parse it as JSON
Reader reader = new InputStreamReader(content);
Wrap wrap = new Gson().fromJson(reader,Wrap.class);
for (Model model : wrap.getModels()) {
adapter.add(model);
}
adapter.notifyDataSetChanged();
content.close();
} catch (Exception ex) {
Log.e(TAG, "Failed to parse JSON due to: " + ex);
failedLoadingPosts();
}
} else {
Log.e(TAG, "Server responded with status code: " + statusLine.getStatusCode());
failedLoadingPosts();
}
} catch(Exception ex) {
Log.e(TAG, "Failed to send HTTP POST request due to: " + ex);
failedLoadingPosts();
}
return null;
I'm getting this exception msg :
Failed to parse JSON due to: java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.ArrayList com.bloomcore.dbnewapp.Wrap.getModels()' on a null object reference
OK, let's work through this logically...
When you get the exception you are seeing it is as a result of...
for (Model model : wrap.getModels()) {
...quite simply wrap is null, trying to call any method on a null object will through a NPE - no arguing about that - you didn't even need to show any other code to figure that one out.
So why is wrap null? Let's look at where it's instantiated (or at least where it SHOULD be)...
Wrap wrap = new Gson().fromJson(reader,Wrap.class);
...so if after calling new Gson().fromJson(...) then wrap turns out to be null, why is that? Well obviously fromJson(...) is returning null.
So, assuming you've read the documentation for fromJson(...) (which I suspect you might not have done), when using a Reader, from the docs...
Returns:
an object of type T from the string. Returns null if json is at EOF.
...it seems your Reader is at EOF. So why would that be?
Basically the HttpResponse doesn't have an HttpEntity with any valid content. Either your server simply isn't returning anything full stop OR the HttpPost doesn't contain valid data in order for the server to return anything valid.
In other words, check your server code and if that's fine, check what you're posting.
You should make sure getModels() is not null when the Object is created. Some Json parsers only use lists but never create them.
private ArrayList<Model> models = new ArrayList<>();
I assume it's trying to add elements to the list which is null.
The app i'm developing downloads a JSON file from remote address at every startup and then it parses the JSON Object and copies data to SQLite on the phone.
This operation is the cause for which the app hangs for some seconds on every startup showing blank screen (or sometime blank and then black screen), in fact if i tried to disable this part of code the app starts quickly, with no hang.
So, how could i do it better?
Here is the code (DataHandler class) related to file download, parsing and writing to local SQLite db:
public class DataHandler {
public DataHandler() {
}
public int storeData(Database db, int num) throws JSONException {
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet("http://www.example.com/data.json");
request.addHeader("Cache-Control", "no-cache");
long id = -1;
try {
HttpResponse response = client.execute(request);
HttpEntity entity = response.getEntity();
InputStreamReader in = new InputStreamReader(entity.getContent());
BufferedReader reader = new BufferedReader(in);
StringBuilder stringBuilder = new StringBuilder();
String line = "";
while ((line=reader.readLine()) != null) {
stringBuilder.append(line);
}
JSONArray jsonArray = new JSONArray(stringBuilder.toString());
SQLiteDatabase dbWrite = db.getWritableDatabase();
ContentValues values = new ContentValues();
if (jsonArray.length() == num && num != 0)
return num;
SQLiteDatabase dbread = db.getReadableDatabase();
dbread.delete("mytable", "1", null);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jObj = (JSONObject) jsonArray.getJSONObject(i);
values.put("_id", jObj.optString("id").toString());
values.put("city", jObj.optString("city").toString());
values.put("country",jObj.optString("country").toString());
values.put("addr", jObj.optString("addr").toString());
values.put("title", jObj.optString("title").toString());
values.put("lon", jObj.optString("lon").toString());
values.put("email", jObj.optString("email").toString());
values.put("phone", jObj.optString("phone").toString());
values.put("web", jObj.optString("web").toString());
values.put("lat", jObj.optString("lat").toString());
values.put("desc", jObj.optString("desc").toString());
values.put("icon", jObj.optString("icon").toString());
values.put("category", jObj.optString("category").toString());
id = dbWrite.insert("merchants", null, values);
}
num = jsonArray.length();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (id > 0)
return num;
else
return -1;
}
}
You should probably to the download and parsing in the background and display some kind of splashscreen with progress information in the meantime.
To avoid an annoying splash screen, you could also cache the data and display your app normally on startup, and only refresh the data once the bakground update is finished.
There are several options to do the download and parse operations in the background :
use an AsyncTask
use a service
I cannot say what's the best solution in your specific case, but I would recommend reading the Processes and Threads and service documentation.
Her goes your Async Task Class
class AsyncClass extends AsyncTask<String,Void,String>
{
int result;
Context context;
ProgressDialog bar;
AsynclassListener<String> listener;
public AsyncClass(Context context, AsynclassListener listener) {//add more parameter as your method body has (i.e Database db, int num) . Don't forget to initialize them.
this.context=context;
this.listener=listener;
bar = new ProgressDialog(context);
bar.setIndeterminate(false);
//make your progressBar here I have just given a simple example for above PB there are more parameters to set.
}
protected String doInBackground(String... Param){
try{
result = storeData();//call your method here
}catch(Exception e){
// Do something when crash
}
return ""+result;
}
#Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
bar.show();// By the time your data fetching and parsing will go on you this progress bar will be visible.
}
#Override
protected void onPostExecute(String result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
bar.dismiss();//As soon as the work is complete the this method is called.
listener.onTaskComplete(""+result);
/**
* In your case you can later typecast back in integer once you recieve the result.
* this listener will post the result to your main activity.
*/
}
}
Here is your Interface
public interface AsynclassListener<T>{
public void onTaskComplete(T result);
}
Now Let your Activity (Splash Class) implement the interface
This will implement the method as :
#Override
public void onTaskComplete(String result) {
// here the asynclass will post the result as 1 or -1 whatever you want.
//After that you may proceed with your next part i.e switching to next activity or so.
}
Edit: I forgot to mention about how this will be called :
new AsyncClass(getApplicationContext(), this).execute("");// here you have to enter the database and other parameter values that will be required to run the method. Change it accordingly.
As you can see here in your method you are fetching the data from net and parsing also :
There is again a second approach in which you can call the network cal in a separate thread and later the parsing can be done further on UIthread.
Also read about the Async Task Class so as to know about the arguments and the working of class.
I'm using the AsyncTask to open a URL, access the server, fetch the content and display them in a list view in the main activity. The content extracted consists of a title of the newspaper and a URL to the website, which will be displayed on a WebView in a second activity, if a "read" button is clicked. I coded out the program straight away and it works, but when I looked back at it, I found something that seems unreasonable, so mainly I want to make clear how the code works. Here is the code for the main activity:
package com.example.newsapp;
public class MainActivity extends Activity {
static final private String LOG_TAG = "main";
private ArrayList<Content> aList;
private class Content{
Content() {};
public String title;
public String url;
}
private class MyAdapter extends ArrayAdapter<Content>{
int resource;
public MyAdapter(Context _context, int _resource, List<Content> titles) {
super(_context, _resource, titles);
resource = _resource;
// this.context = _context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout newView;
final Content content = getItem(position);
// Inflate a new view if necessary.
if (convertView == null) {
newView = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(inflater);
vi.inflate(resource, newView, true);
} else {
newView = (LinearLayout) convertView;
}
// Fills in the view.
TextView tv = (TextView) newView.findViewById(R.id.listText);
ImageButton b = (ImageButton) newView.findViewById(R.id.listButton);
b.setBackgroundResource(0);
tv.setText(content.title);
Typeface type = Typeface.createFromAsset(getAssets(),"LiberationSerif-BoldItalic.ttf");
tv.setTypeface(type);
// Sets a listener for the button, and a tag for the button as well.
b.setTag(Integer.toString(position));
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Reacts to a button press.
Intent intent = new Intent(MainActivity.this, WebPage.class);
Bundle bundle = new Bundle();
bundle.putString("URL", content.url);
intent.putExtras(bundle);
startActivity(intent);
}
});
return newView;
}
}
class MyAsyncTask extends AsyncTask<String, String, String> {
private ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
InputStream inputStream = null;
String result = "";
Content content;
protected void onPreExecute() {
super.onPreExecute();
progressDialog.setMessage("Downloading the news...");
progressDialog.show();
progressDialog.setOnCancelListener(new OnCancelListener() {
public void onCancel(DialogInterface arg0) {
MyAsyncTask.this.cancel(true);
}
});
}
#Override
protected String doInBackground(String... params) {
String url_select = params[0];
ArrayList<NameValuePair> param = new ArrayList<NameValuePair>();
try {
// Set up HTTP post
// HttpClient is more then less deprecated. Need to change to URLConnection
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url_select);
httpPost.setEntity(new UrlEncodedFormEntity(param));
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
// Read content & Log
inputStream = httpEntity.getContent();
} catch (UnsupportedEncodingException e1) {
Log.e("UnsupportedEncodingException", e1.toString());
e1.printStackTrace();
} catch (ClientProtocolException e2) {
Log.e("ClientProtocolException", e2.toString());
e2.printStackTrace();
} catch (IllegalStateException e3) {
Log.e("IllegalStateException", e3.toString());
e3.printStackTrace();
} catch (IOException e4) {
Log.e("IOException", e4.toString());
e4.printStackTrace();
}
// Convert response to string using String Builder
try {
BufferedReader bReader = new BufferedReader(new InputStreamReader(inputStream, "iso-8859-1"), 8);
StringBuilder sBuilder = new StringBuilder();
String line = null;
while ((line = bReader.readLine()) != null) {
sBuilder.append(line + "\n");
}
inputStream.close();
result = sBuilder.toString();
} catch (Exception e) {
Log.e("StringBuilding & BufferedReader", "Error converting result " + e.toString());
}
return result;
} // protected Void doInBackground(String... params)
protected void onPostExecute(String result) {
//parse JSON data
try {
super.onPostExecute(result);
Log.i(LOG_TAG, result);
JSONObject object = new JSONObject(result);
JSONArray jArray = object.getJSONArray("sites");
for(int i=0; i < jArray.length(); i++) {
JSONObject jObject = jArray.getJSONObject(i);
content = new Content();
if (jObject.has("title") && jObject.has("url")){
content.title = jObject.getString("title");
content.url = jObject.getString("url");
aList.add(content);
aa.notifyDataSetChanged();
}
} // End Loop
progressDialog.dismiss();
} catch (JSONException e) {
// progressDialog.dismiss();
Log.e("JSONException", "Error: " + e.toString());
}
} // protected void onPostExecute(String result)
}
private MyAdapter aa;
private MyAsyncTask loadTask;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadTask = new MyAsyncTask();
loadTask.execute("http://luca-ucsc.appspot.com/jsonnews/default/news_sources.json");
aList = new ArrayList<Content>();
aa = new MyAdapter(this, R.layout.list_element, aList);
ListView myListView = (ListView) findViewById(R.id.listView1);
myListView.setAdapter(aa);
aa.notifyDataSetChanged();
}
public void refresh(View v){
if (loadTask.getStatus() == AsyncTask.Status.FINISHED){
aList.clear();
aa.notifyDataSetChanged();
new MyAsyncTask().execute("http://luca-ucsc.appspot.com/jsonnews/default/news_sources.json");
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
So you can see that only after loadTask.execute() in onCreate(), do I create the object for alist and aa, but I'm already using them in onPostExecute() in the AsyncTaks class, so I'm not very clear what happens here, because onPostExecute() and the UI are on the same thread, so the code in onPostExecute() should be executed first.
I thought I should put
aList = new ArrayList<Content>();
aa = new MyAdapter(this, R.layout.list_element, aList);
into onPostExecute(), which is more logical to me, but the app crashes this way. Also I think deleting aa.notifyDataSetChanged(); in onPostExecute() shouldn't be a problem because it's also in the onCreate() method, but this actually causes the list view to be blank, without any content. Actually, putting any of the codes after loadTask.execute() into the if block of the onPostExecute() method causes some problem, or crashes the app. That would be great if somebody can give some insight or hint. Thanks for reading.
onPostExecute is called on the UI thread after the background task completes its work. You cannot guarantee the timing of this call in relation to other calls on the UI thread.
Since you are already implementing getView yourself, I recommend you extend BaseAdapter instead of ArrayAdapter and implement the other few required methods. It's not hard and you can use whatever data structure you want to back the adapter. Assuming you use a List<Content> to back the adapter, you can write a method to swap the list in place like so:
public void swapList(List<Content> newList) {
this.list = newList;
notifyDataSetChanged();
}
In your AsyncTask, you have complete control of the Params, Progress, and Result parameterized types. They don't all have to be String. You can do this instead:
private class myAsyncTask extends AsyncTask<String, Void, List<Content>> {
/* ... */
}
The String for Params is the URL (same as you do now). Void for Progress because you don't publish progress anyway. List<Content> for Result because that's the thing you actually want to end up with after doing your task.
You should do ALL of your work in doInBackground. There is no reason to deserialize a String into a JSONArray and mess around with that in onPostExecute, particularly since that is happening on the main thread. Rewrite doInBackground to return a List<Content>, and all you need in onPostExecute is this:
public void onPostExecute(List<Content> result) {
adapter.swapList(result);
}
Now you can create the adapter once (in onCreate()) and just swap the list whenever it's appropriate.
I am writing an android application, I previously had a problem NetworkOnMainThreadException and I solved using threads. I now don't get any error and also I don't get any output.
here is my code: there is no errors in the LogCat
public class Currency_convert extends Activity {
public int to;
public int from;
public String [] val;
public String s;
public Handler handler;
public double am=0.0;
StringBuilder build=null ;
HttpClient client=null;
HttpGet httpGet =null;
HttpResponse response=null;
HttpEntity entity=null;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.currency);
Spinner s1 = (Spinner) findViewById(R.id.spinner11);
Spinner s2 = (Spinner) findViewById(R.id.spinner22);
final EditText e=(EditText) findViewById(R.id.amountt);
// am=Double.parseDouble(e.getText().toString());
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.name, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.select_dialog_singlechoice);
val = getResources().getStringArray(R.array.value);
s1.setAdapter(adapter);
s2.setAdapter(adapter);
s1.setOnItemSelectedListener(new spinOne(1));
s2.setOnItemSelectedListener(new spinOne(2));
Button b = (Button) findViewById(R.id.button11);
b.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
TextView t = (TextView) findViewById(R.id.textView44);
if(from == to) {
Toast.makeText(getApplicationContext(), "Invalid", 4000).show();
}
else {
try {
s = getJson("http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.xchange%20where%20pair%20in%20(%22"+val[from]+val[to]+"%22)&format=json&diagnostics=true&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=");
//s=getJson("http://www.google.com/ig/calculator?hl=en&q=1USD=?INR");
JSONObject jObj;
jObj = new JSONObject(s);
String exResult = jObj.getJSONObject("query").getJSONObject("results").getJSONObject("rate").getString("Rate");
am=Double.parseDouble(e.getText().toString());
double totalR=(Double.parseDouble(exResult))*am;
String r=String.valueOf(totalR);
t.setText(r);
// Log.println(priority, tag, msg)
System.out.println("r =" +r);
Log.i("hello", r);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
}
public String getJson(final String url)throws ClientProtocolException, IOException {
// private String getJson(String url)throws ClientProtocolException, IOException e {
build = new StringBuilder();
client = new DefaultHttpClient();
httpGet = new HttpGet(url);
new Thread(new Runnable() {
public void run() {
try {
response = client.execute(httpGet);
entity = response.getEntity();
InputStream content = entity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(content));
String con;
while ((con = reader.readLine()) != null) {
build.append(con);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
// response = client.execute(httpGet);
// entity = response.getEntity();
// InputStream content = entity.getContent();
// BufferedReader reader = new BufferedReader(new InputStreamReader(content));
// String con;
// while ((con = reader.readLine()) != null) {
// build.append(con);
// }
return build.toString();
//return url;
}
private class SpinOne implements OnItemSelectedListener {
int ide;
SpinOne(int i) {
ide =i;
}
public void onItemSelected(AdapterView<?> parent, View view,
int index, long id) {
if(ide == 1) {
from = index;
}
else if(ide == 2) {
to = index;
}
}
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
}
}}
The way it is written, getJson() will return immediately without giving time for the thread to run completely, so the returned value will not be what you want. Use an AsyncTask so you can run your thread code in the AsyncTask's doInBackground() method and then pass the result to the onPostExecute() method where you can then perform setText() as you intend.
An alternative is to move the JSON parsing and setText() code into the thread's run() method after the HTTP request is made but since running UI-related code (in this case setText()) in a separate thread is not allowed you can use a Handler to schedule setText() to run in the UI thread.
You can read the basics on AsyncTask and Handler here.
When you spawn a thread, code execution splits into different time frames, so even though global scope is shared, you won't get objects populated in a timely fashion for your UI update task if you don't implement some logic to prevent inconsistencies.
Android provides multiple flow control and inter-thread communication patterns built-in that can help you solve such inconsistencies. One such option involves AsyncTask, in your case you can do the following:
Extended AsyncTask with your UI thread-forbidden tasks inside the doInBackground() method;
Get logic that needs to run on UI thread (such as manipulating Views) inside onPostExecute() handler from the same AsyncTask instance. This handler will only be called after doInBackground returns, so the program knows that the logic to populate the object was triggered.
You can look up a sample of AsyncTask in this answear for a practical approach.
Note: If you want to use parent class members such as findViewByID inside an AsyncTask instance, you will need to manually invoke the parent file scope using the <UIThreadName>.this., e.g. <UIThreadName>.this.findViewByID(id). You can do this freely in onPostExecute which has no restrictions due to running on the UI thread, but you are restricted to not performing UI changes in doInBackground (which doesn't run on the UI thread).
I solved it, I just added t.join after the thread declaration :)
I'm trying to add an animation to my ListView so text will fade into separate rows as results come in from an HTTP GET request. I know how to do the fade in effect and i already have a custom ListView adapter but the problem is that the ListView updates all the rows each time a result comes in, thus triggering the fade in effect each time for the entire list.
How would I be able to control a single row so the ListView won't animate every row on each data change?
This is the code i use to fill the ListView:
private class CustomAdapter extends ArrayAdapter<Row> {
public CustomAdapter() {
super(Results.this, R.layout.row, dataList);
}
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
RowHolder holder = null;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.row, parent, false);
holder = new RowHolder(row);
row.setTag(holder);
} else {
holder = (RowHolder) row.getTag();
}
try {
holder.populateRow(dataList.get(position));
} catch (NullPointerException e) {
e.printStackTrace();
}
return row;
}
}
private class RowHolder {
private TextView label = null;
private TextView count = null;
private TextView result = null;
public RowHolder(View row) {
label = (TextView) row.findViewById(R.id.list_label);
count = (TextView) row.findViewById(R.id.list_count);
result = (TextView) row.findViewById(R.id.list_result);
}
public void populateRow(Row r) {
label.setText(r.getLabel());
count.setText(r.getCount());
result.setText(r.getResult());
label.startAnimation(fadeIn);
count.startAnimation(fadeIn);
result.startAnimation(fadeIn);
}
}
Any help is appreciated, thank you in advance!
Edit 1:
My AsyncTask:
private class CheckSource extends AsyncTask<String, Void, String> {
protected void onPreExecute() {
results.setUnixTime(getUnixTime());
results.setLabel(getString(R.string.label));
results.setCount(null);
results.setResult(null);
results.setResultLabel("");
results.setShowProgress(true);
results.setIconType(null);
results.setShowIcon(false);
results.setHasResults(false);
adapter.notifyDataSetChanged();
}
#Override
protected String doInBackground(String... params) {
String query = params[0];
String httpResults = null;
try {
httpResults = getResults(query, "source");
jsonObject = new JSONObject(httpResults);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return httpResults;
}
protected void onPostExecute(String results) {
try {
parseJSON(results);
} catch (JSONException e) {
e.printStackTrace();
results.setResultLabel("<br />"
+ getString(R.string.source_not_available) + "<br />");
}
results.setShowProgress(false);
adapter.notifyDataSetChanged();
}
// Parse the retrieved json results
private void parseJSON(String jsonResults) throws JSONException {
if (jsonResults == null) {
results.setResult(null);
results.setHasResults(false);
results.setResultLabel("<br />"
+ getString(R.string.source_not_available) + "<br />");
return;
}
jsonObject = new JSONObject(jsonResults);
String result = null;
String resultLabel = null;
switch (jsonObject.getInt("count")) {
case -1:
results.setCount(null);
results.setHasResults(false);
resultLabel = getString(R.string.no_results);
break;
case 0:
results.setCount(null);
results.setHasResults(false);
resultLabel = getString(R.string.no_results);
break;
case 1:
results.setHasResults(true);
results.setCount(jsonObject.get("count").toString() + " "
+ getString(R.string.one_result));
result = jsonObject.get("url").toString();
resultLabel = getString(R.string.hyperlink_text);
break;
default:
results.setHasResults(true);
results.setCount(jsonObject.get("count").toString() + " "
+ getString(R.string.multiple_results));
result = jsonObject.get("url").toString();
resultLabel = getString(R.string.hyperlink_text);
break;
}
results.setResult(result);
results.setResultLabel("<br />" + resultLabel + "<br />");
}
}
The method that executes the HTTP request:
private String getResults(String query, String source)
throws IllegalStateException, IOException, URISyntaxException {
/* Method variables */
StringBuilder builder = new StringBuilder();
String URL = "url";
URI uri;
String phrase = "phrase";
List<NameValuePair> params = new ArrayList<NameValuePair>();
/* HTTP variables */
HttpGet httpGet;
DefaultHttpClient httpClient;
HttpResponse httpResponse;
HttpEntity httpEntity;
HttpParams httpParams;
int socketTimeout = 10000;
int connectionTimeout = 10000;
// Set the socket and connection timeout values
httpParams = new BasicHttpParams();
HttpConnectionParams
.setConnectionTimeout(httpParams, connectionTimeout);
HttpConnectionParams.setSoTimeout(httpParams, socketTimeout);
httpClient = new DefaultHttpClient(httpParams);
// Add parameters to the GET request
params.add(new BasicNameValuePair("query", query));
params.add(new BasicNameValuePair("type", source));
String paramString = URLEncodedUtils.format(params, "utf-8");
uri = new URI(URL + paramString);
httpGet = new HttpGet(uri);
// Execute the GET request
httpResponse = httpClient.execute(httpGet);
/* Read http response if http status = 200 */
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
httpEntity = httpResponse.getEntity();
InputStream content = httpEntity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(
content));
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
}
return builder.toString();
}
As Romain Guy explained a while back during the Google I/O session, the most efficient way to only update one view in a list view is something like the following (this one update the whole view data):
ListView list = getListView();
int start = list.getFirstVisiblePosition();
for(int i=start, j=list.getLastVisiblePosition();i<=j;i++)
if(target==list.getItemAtPosition(i)){
View view = list.getChildAt(i-start);
list.getAdapter().getView(i, view, list);
break;
}
Assuming target is one item of the adapter.
This code retrieve the ListView, then browse the currently shown views, compare the target item you are looking for with each displayed view items, and if your target is among those, get the enclosing view and execute the adapter getView on that view to refresh the display.
As a side note invalidate doesn't work like some people expect and will not refresh the view like getView does, notifyDataSetChanged will rebuild the whole list and end up calling getview for every displayed items and invalidateViews will also affect a bunch.
One last thing, one can also get extra performance if he only needs to change a child of a row view and not the whole row like getView does. In that case, the following code can replace list.getAdapter().getView(i, view, list); (example to change a TextView text):
((TextView)view.findViewById(R.id.myid)).setText("some new text");
In code we trust.
Method notifyDataSetChanged force to call getView method to all visible elements of the ListView. If you want update only 1 specific item of the ListView you need to path this item to the AsynhTask.