Reputation: 4595
Please don't close this, IMHO it is decent and possibly useful programming question.
Please I am reading a lot of stuff, and I am getting confused because I read different opinions and different approaches.
The problem is the following:
in the getView()
of an Adapter
I need to perform some asynchronous operation, like checking an formation on the web, and update the view based on that.
I used the following approach:
every time getView()
is called I start a Thread
but my approach as earned me lots of criticism:
https://stackoverflow.com/a/28484345/1815311
https://stackoverflow.com/a/28484335/1815311
https://stackoverflow.com/a/28484351/1815311
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
//...
}
else {
//...
}
Thread th= new Thread(new Runnable() {
@Override
public void run() {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
CheckSomeInfoOverTheInternet(url, new myCallback {
@Override
public void onSuccess() {
holder.textview.setText("OK");
}
@Override
public void onFailre() {
holder.textview.setText("NOT OK!!!!");
}
});
}
});
}
});
th.start();
return convertView;
}
Please what would be the best practice for doing such a thing?
Please note, I am not looking for a solution to execute the network requests in getView()
, but rather, how to updated the view depending on the result on the asynchronous call.
Upvotes: 7
Views: 4939
Reputation: 3021
EDIT
I think this is an interesting question, worth some kind of "canonical" solution
Google I/O 2013 :P I suggest you to watch this Google I/O from 2013. They have clealy explained a lot of these stuff there. All of your questions will be answered there. It's canon.
I have used Volley library here. If you read the docs then you'll see that Volley runs on background threads. So no need to implement your async tasks. Since others have already covered the issues in using Threads I will not talk about those. Let me go into code directly :)
The following has served me well whenever my listviews or gridviews or any other views depend on information from the web:
Create an interface: WebInfoUpdateReceiver.java
public interface WebInfoUpdateReceiver {
public void receive(Foo [] fooItems);
}
Create a class to download stuff: Downloader.java
public class Downloader {
private Context mContext;
private WebInfoUpdateReceiver mReceiver;
public Downloader(WebInfoUpdateReceiver receiver) {
mReceiver = receiver;
}
public void downloadStuff() {
MyStringRequest request = new MyStringRequest(Request.Method.GET,requestUrl, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// parse the response into Foo[]
mReceiver.update(foo);
}
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
RequestQueue queue = Volley.newRequestQueue(mContext);
queue.add(request);
}
}
Now make your activity implement the interface:
public class Activity extends Activity implements WebInfoUpdateReceiver {
public void receive(Foo [] fooItems) {
// convert the array into arrayList
adapter.insert(arraylist);
adapter.notifyDataSetChanged();
}
}
Upvotes: 4
Reputation: 1868
So, taking a step back and looking at this completely abstractly, it seems the question you are asking is along the lines of how to manage some potentially long-running action (network or otherwise) that will affect the appearance of a list view item.
Given that assumption, you face two main problems:
Issue 1 can be addressed by using a ThreadPoolExecutor (or whatever other mechanisim you feel like). The idea being that the threads are recycled as well, so they don't take so much time to spin up.
Issue 2 is a little more complicated. You can cancel the thread operation when the view is about to be recycled (there are a few ways of doing this) but you need to decide if losing the effort is acceptable (the user may scroll your view back on the screen and you have to start again). You could store the result of your long-running task against the list item (or some data holder associated with the list item) but you need to be wary of running out of memory (least recently used caching or lru caches can sometimes be useful here). This would allow you to keep your effort and only update your list view if it is still on-screen. A flag in your item data could be used to indicate you already have the data and not load it again.
Sorry, I don't have enough time to go into more detail at the moment. It's bedtime for me :-)
Good luck. I'll write some more if I have time. Kind regards, CJ
Upvotes: 1
Reputation: 2129
IMHO, the best practice would be not to perform asynchronous operations in getView(). Your Adapter class is only supposed to transform Objects into Views and you should keep it as straightforward as possible.
You can run into trouble if you start an asynchronous task (or a Thread) which references newly created View if the task finishes after the view is not on screen anymore or if it was recycled by the adapter as a result of scrolling.
You should consider performing asynchronous operations outside of the adapter. Start an operation and update the list after it is done. You can update the whole list or some elements when the asynchronous operation is finished.
Operations themselves can be done in AsyncTask of your activity or in a dedicated Service. Avoid creating new Threads because creating threads is expensive and if for some reason you call it too often, you can run out of memory. AsyncTasks use a Thread pool and therefore you will not have this problem.
Upvotes: 1
Reputation: 113
Volley library seems to have abstracted out some of the common patters for use...
For finer control over network requests and caching , you could have a look at : http://developer.android.com/training/volley/simple.html#send
Like in the architechture diagram, on making a request, it looks up cache, and in case of misses tries the network request and adds it to cache for later use, Moreover it seems you can provide custom back off-retry request suiting your needs and handle/invalidate cache as and when required.
For in-depth reference you could take a look at - https://android.googlesource.com/platform/frameworks/volley/+/master/src/main/java/com/android/volley
Upvotes: 1
Reputation: 878
You can use something like this:
public View getView(int position, View convertView,
ViewGroup parent) {
ViewHolder holder;
...
holder.position = position;
new ThumbnailTask(position, holder)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
return convertView;
}
private static class ThumbnailTask extends AsyncTask {
private int mPosition;
private ViewHolder mHolder;
public ThumbnailTask(int position, ViewHolder holder) {
mPosition = position;
mHolder = holder;
}
@Override
protected Cursor doInBackground(Void... arg0) {
// Download bitmap here
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (mHolder.position == mPosition) {
mHolder.thumbnail.setImageBitmap(bitmap);
}
}
}
private static class ViewHolder {
public ImageView thumbnail;
public int position;
}
Also, for even better performance you need to add interaction awareness to your ListView
adapter so that it doesn’t trigger any asynchronous operation per row after, say, a fling gesture on the ListView
—which means that the scrolling is so fast that it doesn’t make sense to even start any asynchronous operation. Once scrolling stops, or is about to stop, is when you want to start actually showing the heavy content for each row.
A very good example can be found here: https://code.google.com/p/shelves/
Upvotes: 1
Reputation: 3504
There are several appraoches for this. Although what you are doing is really not appropriate.
Handlers and Threads
In conclusion,the major drawbacks of your current approach: - is loss of context when the changes need to be made. - the explicit creation of multiple threads
While the latter is a major issue,the more "user-noticeable" problem would the first.
There are and could be several other approaches based on the control you require and the your expertise with android callbacks and thread management.But these three are(according to me) most suitable.
PS:the common point in all these approaches is,
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
//...
}
else {
//...
}
//execute a task for your given id where task could be:
//1. AsyncTask
//2. AsyncTaskLoader
//3. Handlers and thread
//call notifyDataSetChanged() in all the cases,
return convertView;
}
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
//do any tasks which you feel are required
}
PPS:You could also look into DataSetObserver to again automate your requirements.
Upvotes: 2
Reputation: 8338
This is absolutely not a good way to go about updating information in a ListView
. The getView
method should simply create the view from data that you already have. It certainly shouldn't be running anything to get more information.
The best advice that I could give you is to fetch the data beforehand. Pull the data, update the ArrayList
that your Adapter
is connected to, then call adapter.notifyDataSetChanged()
. This will redraw all of your information.
Pull the data all at once - not in small parts. This is the best and most reasonable way to do this.
Upvotes: 5