How to update Android ListView with dynamic data in real time?

21,502

Solution 1

I experimented with ListView, and you essentially have to update the ListView cells manually without calling notifyDataSetChanged() if you have realtime data and you want the ListView to update with better performance.

notifyDataSetChanged() causes the ListView to rebuild its entire View hierarchy is very slow if you are calling it frequently (i.e. once every second).

Note (2014). This DOES NOT APPLY if you are using normal modern ListView with a ViewHolder pattern. You simply call 'notifyDataSetChanged' and that's all there is to it. It is incredibly efficient as Android knows to only update the cells on the screen.

I implemented a scheme where if my data set size changed from one update to the next, I call notifyDataSetChanged(). If the data set size remained constant from one update to the next (such that the number of cells in the ListView is the same as before when a redraw of the data is needed), then I iterate over the ListView.getFirstVisiblePosition() : getLastVisiblePosition(), and update the visible cells only.

Solution 2

I once implemented a filter like the code beolow using notifyDataSetChanged() and had no problems with it.

I've also modified the views of a List on the go manually. Both have worked well. In some case I prefear to modify the data manually because its faster and because itdoesn't affect the whole list.

Anyway, views are created on the go when they need to apear on the screen and are deleted when they leave the screen, so if you modify the data used to create the views, if the user scrolls the ListView and the views get out of the screen, in theory, the views will be created with the new data once they come again into the screen.

I would recommend you to try the code below to understand how does notifyDataSetChanged() work and decide if it works for you.

public class ListText extends Activity {


    private ListView lv1;
    private Followed followedFriends[];
    ListView lst;
    EditText edt;
    FollowedFilterableAdapter arrad;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        lv1=(ListView)findViewById(R.id.listView1);
        edt = (EditText) findViewById(R.id.editText1);

        followedFriends = new Followed[10];
        followedFriends[0] = new Followed("Alan Walder", "0123456789", "1");
        followedFriends[1] = new Followed("Alberto Levi", "123456789", "1");
        followedFriends[2] = new Followed("David Rodan", "23456789", "1");
        followedFriends[3] = new Followed("David Stern", "3456789", "1");
        followedFriends[4] = new Followed("Elias Jawa", "456789", "1");
        followedFriends[5] = new Followed("Elian Moreno", "56789", "1");
        followedFriends[6] = new Followed("Jonathan Rodan", "6789", "1");
        followedFriends[7] = new Followed("Klara Rodan", "789", "1");
        followedFriends[8] = new Followed("Willy Rosales", "89", "1");
        followedFriends[9] = new Followed("ZZZ ZZZ", "9", "1");


        arrad =  new FollowedFilterableAdapter(followedFriends);
        lv1.setAdapter(arrad);

        addTextChangeList();
    }

    private void addTextChangeList()
    {
        edt.addTextChangedListener(new TextWatcher()
        {


            public void onTextChanged( CharSequence arg0, int arg1, int arg2, int arg3)
            {
                // TODO Auto-generated method stub

            }

            public void beforeTextChanged( CharSequence arg0, int arg1, int arg2, int arg3)
            {
                // TODO Auto-generated method stub

            }



            public void afterTextChanged( Editable arg0)
            {
                // TODO Auto-generated method stub
                ListText.this.arrad.getFilter().filter(arg0);
            }
        });

    }


    ///////////////////////////////////Internal classes ////////////////////////

    private class Followed
    {
        private String _name;
        private String _id;
        private boolean _followed;
        private String _picToDelete = "http://images.wikia.com/tibia/en/images/7/72/Skeleton.gif";

        private Followed(String name, String id, String followed)
        {
            this._name = name;
            this._id = id;
            this._followed = followed.equals("1");
        }

        public String toString(){return _name;}
        public String getName(){return _name;}
        public String getId(){return _id;}
        public boolean idFollowed(){return _followed;}
        public String getURL(){return _picToDelete;}
    }

    /////////////////////////////////////////Adapter//////////////////////////////////

    private class FollowedFilterableAdapter extends BaseAdapter implements Filterable
    {
        /**
         * Lock used to modify the content of {@link #mObjects}. Any write operation
         * performed on the array should be synchronized on this lock. This lock is also
         * used by the filter (see {@link #getFilter()} to make a synchronized copy of
         * the original array of data.
         */
        private final Object _Lock = new Object();

        /*/**
         * Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever
         * {@link #mObjects} is modified.
         */
        //private boolean _NotifyOnChange = true;

        private List<Followed> _Objects;

        private ArrayList<Followed> _followedFriends;
        private ArrayFilter _Filter;

        public FollowedFilterableAdapter(Followed[] followedFriends)
        {
            _Objects = Arrays.asList(followedFriends);
        }

        public int getCount() {
            return _Objects.size();
        }

        public Followed getItem(int position) {
            return _Objects.get(position);
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            int px = 2;

            //Creating the CategoryRow that represents the row
            LinearLayout lstItem = new LinearLayout(ListText.this);
            lstItem.setLayoutParams(new ListView.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT,1));
            lstItem.setOrientation(LinearLayout.HORIZONTAL);
            //lstItem.setPadding(px,px,px,px);
            lstItem.setGravity(Gravity.CENTER_VERTICAL);
            /*<LinearLayout android:layout_width="fill_parent"
                            android:layout_height="wrap_content" android:orientation="horizontal"
                            android:padding="2dp" android:gravity="center_vertical">*/

            //Adding the Image
            ImageView icon = new ImageView(ListText.this);
            icon.setLayoutParams(new LayoutParams(50,50));
            icon.setImageResource(R.drawable.icon);
            icon.setScaleType(ScaleType.CENTER_CROP);
            //icon.setImage(tag.getId());
            /*<ImageView android:layout_width="50dp" android:id="@+id/iconFollList"
                                android:src="@drawable/icon" android:layout_height="40dp"></ImageView>*/

            //Adding the Linear Layout for the text
            RelativeLayout lstTextx = new RelativeLayout(ListText.this);
            lstTextx.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT,1));
            lstTextx.setGravity(Gravity.CENTER_VERTICAL);
            lstTextx.setPadding(5, px, px, px);
            /*<LinearLayout android:layout_width="fill_parent"
                                android:layout_height="wrap_content" android:orientation="vertical"
                                android:padding="2dp" android:paddingLeft="5dp">*/

            //Adding the Name of the person who commented
            TextView txtCommenter = new TextView(ListText.this);
            txtCommenter.setLayoutParams(
                    new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
                    LayoutParams.WRAP_CONTENT));
            txtCommenter.setTextSize(10);
            txtCommenter.setTypeface(Typeface.create("Sans Serif", Typeface.BOLD));
            txtCommenter.setText(_Objects.get(position).getName());
            /*<TextView android:text="TextView" android:id="@+id/FollListCategory"
                                    android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>*/


            ImageView btnFollowed = new ImageView(ListText.this);
            RelativeLayout.LayoutParams params = 
                new RelativeLayout.LayoutParams(80,30);
            params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            btnFollowed.setLayoutParams(params);
            btnFollowed.setImageResource(R.drawable.icon);
            btnFollowed.setScaleType(ScaleType.FIT_XY);

            //Arming the View
            lstItem.addView(icon, 0);
            lstTextx.addView(txtCommenter, 0);
            lstTextx.addView(btnFollowed,1);
            lstItem.addView(lstTextx,1);

            return lstItem;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void notifyDataSetChanged() {
            super.notifyDataSetChanged();
            //_NotifyOnChange = true;
        }

        /*public void setNotifyOnChange(boolean notifyOnChange) {
            _NotifyOnChange = notifyOnChange;
        }*/

        public Filter getFilter() {
            if (_Filter == null) {
                _Filter = new ArrayFilter();
            }
            return _Filter;
        }

        /////////////////////Second Level Internal classes //////////////////////////

        private class ArrayFilter extends Filter {
            @Override
            protected FilterResults performFiltering(CharSequence prefix) {
                FilterResults results = new FilterResults();

                if (_followedFriends == null) {
                    synchronized (_Lock) {
                        _followedFriends = new ArrayList<Followed>(_Objects);
                    }
                }

                if (prefix == null || prefix.length() == 0) {
                    synchronized (_Lock) {
                        ArrayList<Followed> list = new ArrayList<Followed>(_followedFriends);
                        results.values = list;
                        results.count = list.size();
                    }
                } else {
                    String prefixString = prefix.toString().toLowerCase();

                    final ArrayList<Followed> values = _followedFriends;
                    final int count = values.size();

                    final ArrayList<Followed> newValues = new ArrayList<Followed>(count);

                    for (int i = 0; i < count; i++) {
                        final Followed value = values.get(i);
                        final String valueText = value.toString().toLowerCase();

                        // First match against the whole, non-splitted value
                        if (valueText.startsWith(prefixString)) {
                            newValues.add(value);
                        } else {
                            final String[] words = valueText.split(" ");
                            final int wordCount = words.length;

                            for (int k = 0; k < wordCount; k++) {
                                if (words[k].startsWith(prefixString)) {
                                    newValues.add(value);
                                    break;
                                }
                            }
                        }
                    }

                    results.values = newValues;
                    results.count = newValues.size();
                }

                return results;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                //no inspection unchecked
                _Objects = (List<Followed>) results.values;
                if (results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
        }
    }

xml

    <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <EditText android:text="" android:layout_height="wrap_content"
    android:id="@+id/editText1" android:layout_width="match_parent"></EditText>
    <ListView android:id="@+id/listView1" android:layout_height="fill_parent"
    android:layout_width="match_parent" android:layout_weight="1"></ListView>

</LinearLayout>

Hope this helps.

Solution 3

Maybe you can set a Handler on the UI thread. You need to create a class which implements Runnable. Pass an ArrayList to this class. In the run() method create the adaptor with the ArrayList as a parameter, then do a setAapter on the ListView. That's it. You're done. To launch your handler : just to this from your worker thread : handler.Post(new MyUpdateToUI() ); That's it. I hope it is efficient enough for you?

Share:
21,502
Android Dev
Author by

Android Dev

Updated on July 03, 2020

Comments

  • Android Dev
    Android Dev almost 4 years

    I have a background thread loading data which I want to display in an Android ListView. The data changes very often (i.e. 1-2 times per second). Sometimes the number of rows in the dataset changes too (but certainly not as often as the data in the cells changes).

    There are two ways to update the data in the cells, as far as I can tell:

    1. Have the background thread notify the UI thread that new data is ready, and the UI thread can then call BaseAdapter.notifyDataSetChanged(). However, I have read in more than one place that if that method is called often, it will be slow, because the ListView has to restructure all of its subviews Views.

    2. If the dataset count has not changed, I could possibly find all of the visible ListView cells that are associated with the changed data, and update the values manually without calling notifyDataSetChanged(). This would probably work, but I think its unfortunate that I have to update the views manually when the List Adapter is supposed to handle the update notifications and mechanisms for me when I notify it. This method also won't work if the dataset count changes over time (i.e. not only is the data within each cell of the ListView changing, but the total number of cells in the ListView can grow or shrink based on the background thread supplying realtime data).

    I would certainly appreciate thoughts from others who have implemented this scenario, and how they optimized code simplicity and most importantly, performance.

  • J-Rou
    J-Rou about 13 years
    I forgot something. In this scenario i used a fixed picture, but in my program I download the pictures and i show a loading picture until the pictre is downloaded, then I modify it manually by locating the view. It works just like i expect it to work (With a followedFriends array of more than 700 friends). I don't use the notifyDataSetChanged() to update the pictures because its not neccessary and bydoing it manually the program doesnt get slow.
  • pgsandstrom
    pgsandstrom almost 13 years
    A ListView does not hold one view for each object in its adapter. The views are re-used, so the visible views are the only one there are. So your implementation is not optimizing anything.
  • Jay Soyer
    Jay Soyer over 11 years
    You should look into the ViewHolder pattern for Adapters