Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: best practice to perform asynchronous operations in getView()

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 AdapterI 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.

like image 735
Lisa Anne Avatar asked Feb 13 '15 16:02

Lisa Anne


4 Answers

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.

like image 142
Alex K Avatar answered Nov 16 '22 12:11

Alex K


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:

  1. Create an interface: WebInfoUpdateReceiver.java

    public interface WebInfoUpdateReceiver {
    
        public void receive(Foo [] fooItems);
    
    }
    
  2. 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);
        }
    
    }
    
  3. 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();
    }
      }
    
like image 35
denvercoder9 Avatar answered Nov 16 '22 12:11

denvercoder9


There are several appraoches for this. Although what you are doing is really not appropriate.

  1. AsyncTask

    • The thread pooling here is done internally,so you do not need to bother with that
    • Its a more cleaner approach to your problem instead of spawning individual threads.
    • If your user changes the screen during your API call ,you can also cancel the call.
    • You would have to enable notifyDatasetChanged()
    • You need to override very few functions to achieve the functionality that you want.
  2. AsyncTaskLoader

    • It gives you more control but you lose out on several implicitly defined functions
    • You need more knowledge to use this and should be well versed with classes like LoaderManager,Loader.
    • change is self trigerring Say if you were to change your underlying dataset,the changes would automatically trigger and provide a change to your UI.
  3. Handlers and Threads

    • This is one stpe above your current appraoch but provide way more benifits
    • You can abstract the thread creation and provide a handler which would handle the call for all your ids.
    • You could queue the threads and the messages delivered.
    • if the screen changes,you could remove callbacks and messages.

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.

like image 2
Droidekas Avatar answered Nov 16 '22 12:11

Droidekas


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/

like image 1
Vamsi Avatar answered Nov 16 '22 13:11

Vamsi