Search code examples
androidlistviewcountdownbaseadaptercountdowntimer

How to handle multiple countdown timers in ListView?


I have a listview (with a custom list adapter), I need to display a countdown on every row.

For example, if my list contains 4 items, I will have 4 rows. At this point, I need to handle 4 different countdowns (one for each row) because time is different.

enter image description here

So far, I'm handling it the following way : in the Custom List Adapter, inside getView() method I create a new CountDownTimer and display remaining time inside TextView.

But the problem is that it slows the activity a lot, I can't even scroll correctly in the worst cases (because each time a row is displayed, it creates a new CountDownTimer).

I searched a lot for a better solution, but no one was satisfying.

Is there a cleaner and smoother solution to handle multiple countdown timers inside a listView ?

Thanks


Solution

  • Instead of trying to show the remaining time for all, the idea is to update the remaining time for the items which are visible.

    Please follow the following sample code and let me know :

    MainActivity :

    public class MainActivity extends Activity {
    
    private ListView lvItems;
    private List<Product> lstProducts;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        lvItems = (ListView) findViewById(R.id.lvItems);
        lstProducts = new ArrayList<>();
        lstProducts.add(new Product("A", System.currentTimeMillis() + 10000));
        lstProducts.add(new Product("B", System.currentTimeMillis() + 20000));
        lstProducts.add(new Product("C", System.currentTimeMillis() + 20000));
        lstProducts.add(new Product("D", System.currentTimeMillis() + 20000));
        lstProducts.add(new Product("E", System.currentTimeMillis() + 20000));
        lstProducts.add(new Product("F", System.currentTimeMillis() + 20000));
        lstProducts.add(new Product("G", System.currentTimeMillis() + 30000));
        lstProducts.add(new Product("H", System.currentTimeMillis() + 20000));
        lstProducts.add(new Product("I", System.currentTimeMillis() + 20000));
        lstProducts.add(new Product("J", System.currentTimeMillis() + 40000));
        lstProducts.add(new Product("K", System.currentTimeMillis() + 20000));
        lstProducts.add(new Product("L", System.currentTimeMillis() + 50000));
        lstProducts.add(new Product("M", System.currentTimeMillis() + 60000));
        lstProducts.add(new Product("N", System.currentTimeMillis() + 20000));
        lstProducts.add(new Product("O", System.currentTimeMillis() + 10000));
    
        lvItems.setAdapter(new CountdownAdapter(MainActivity.this, lstProducts));
    }
    
    private class Product {
        String name;
        long expirationTime;
    
        public Product(String name, long expirationTime) {
            this.name = name;
            this.expirationTime = expirationTime;
        }
    }
    
    
    public class CountdownAdapter extends ArrayAdapter<Product> {
    
        private LayoutInflater lf;
        private List<ViewHolder> lstHolders;
        private Handler mHandler = new Handler();
        private Runnable updateRemainingTimeRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (lstHolders) {
                    long currentTime = System.currentTimeMillis();
                    for (ViewHolder holder : lstHolders) {
                        holder.updateTimeRemaining(currentTime);
                    }
                }
            }
        };
    
        public CountdownAdapter(Context context, List<Product> objects) {
            super(context, 0, objects);
            lf = LayoutInflater.from(context);
            lstHolders = new ArrayList<>();
            startUpdateTimer();
        }
    
        private void startUpdateTimer() {
            Timer tmr = new Timer();
            tmr.schedule(new TimerTask() {
                @Override
                public void run() {
                    mHandler.post(updateRemainingTimeRunnable);
                }
            }, 1000, 1000);
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = lf.inflate(R.layout.list_item, parent, false);
                holder.tvProduct = (TextView) convertView.findViewById(R.id.tvProduct);
                holder.tvTimeRemaining = (TextView) convertView.findViewById(R.id.tvTimeRemaining);
                convertView.setTag(holder);
                synchronized (lstHolders) {
                    lstHolders.add(holder);
                }
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
    
            holder.setData(getItem(position));
    
            return convertView;
        }
    }
    
    private class ViewHolder {
        TextView tvProduct;
        TextView tvTimeRemaining;
        Product mProduct;
    
        public void setData(Product item) {
            mProduct = item;
            tvProduct.setText(item.name);
            updateTimeRemaining(System.currentTimeMillis());
        }
    
        public void updateTimeRemaining(long currentTime) {
            long timeDiff = mProduct.expirationTime - currentTime;
            if (timeDiff > 0) {
                int seconds = (int) (timeDiff / 1000) % 60;
                int minutes = (int) ((timeDiff / (1000 * 60)) % 60);
                int hours = (int) ((timeDiff / (1000 * 60 * 60)) % 24);
                tvTimeRemaining.setText(hours + " hrs " + minutes + " mins " + seconds + " sec");
            } else {
                tvTimeRemaining.setText("Expired!!");
            }
        }
    }
    }
    

    activity_main.xml :

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <ListView
        android:id="@+id/lvItems"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    
    </RelativeLayout>
    

    list_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp">
    
    <TextView
        android:id="@+id/tvProduct"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Product Name"
        android:textSize="16dp"
        android:textStyle="bold" />
    
    <TextView
        android:id="@+id/tvTimeRemaining"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Time Remaining : " />
    
    </LinearLayout>