3

My use case is very simple, I have a RecyclerView and each item in RecyclerView has a timer which is supposed to run a countdown to 10 seconds (for example) and update the TextView in that ViewHolder every second to show the time left in seconds.

I created a timer class as following

public class BurnTimerTask2
    {
        private System.Timers.Timer t = new System.Timers.Timer(1000);
        private int Secs;
        private TextView tv;
        public BurnTimerTask2(int Secs, TextView tv)
        {
            this.Secs = Secs;
            this.tv = tv;
            t.Elapsed += T_Elapsed;
            t.Start();
        }

        private void T_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Android.Util.Log.Debug("PMBurnTimers", "Timer: " + Secs);
            Secs--;
            tv.Text = Secs + " Seconds left";
            if (Secs == 0)
                t.Stop();
        }
    }

In my OnBindViewHolder, I am calling it like this

if (!MYLIST[position].TimerTriggered)
{                                       
  MYLIST[position].TimerTriggered= true;
  new BurnTimerTask2(10, viewHolder.Textview);
}
else
{                   
  viewHolder.Textview.Text = "TIMER ALREADY TRIGGERED";                                      
}

The new BurnTimerTask2(10, viewHolder.Textview); is where I am calling the timer class and passing the TextView (In the above code I am checking if timer has been triggered or not, If it has been triggered, I don't call the timer class so I don't have multiple timers for the same item)

ISSUES:

1- TextView which shows the seconds does not update unless I scroll the RecyclerView slightly, once I slightly scroll the RecyclerView it starts ticking and updates text for TextView as expected.

2- As soon as Timer class is called, the UI in my Activity gets non-responsive, I cannot interact with anything outside the RecyclerView for few seconds and when I can it is so slow, if I click a button, it takes forever for the action on that button to trigger. (Note: This only happens when I update the TextView, If I do not update the TextView and run the timer by itself, everything works fine so definalty not the timer which is causing it but updating the TextView from the timer causing it)

Obviously I not updating the TextView the right way. I tried the following

1- Run the timer within the adapter (same issues)

2- Create a timer for each item within ViewHolder pointed out here Countdown timer in recyclerview not working properly (same issues)

According to the following articles, what I am doing should just simply work but it doesn't How to handle multiple countdown timers in RecyclerView? and https://github.com/Manikkumar1988/TimerInRecyclerView

I am not sure how to tackle this issue. Any idea

Ali
  • 2,702
  • 3
  • 32
  • 54
  • You can use a Single Timer in adapter use `notifyItemChange` with payload . – ADM Apr 06 '21 at 02:42
  • @ADM how can use a single timer in Adapter if I have all items running a different countdown (10 secs, 20 secs, 30 sec etc) timer? – Ali Apr 06 '21 at 04:29
  • A single Timer will Provide call of each second pass . So if you have the start time of each item you can just calculate the Current difference and set it to text view . – ADM Apr 06 '21 at 04:31
  • @ADM understood but when I have 3 items visible (for example) and if I am calculating the time difference in `onBindViewHolder` , how do I do that for 3 items at the same time when I only have one instance of timer ticking? – Ali Apr 06 '21 at 04:49
  • https://stackoverflow.com/questions/24989218/get-visible-items-in-recyclerview – ADM Apr 06 '21 at 04:51
  • @ADM Getting the visible item in `RecyclerView` is not an issue but I have 3 or 4 items visible at the same time, how do I tackle 1 instance of timer in 4 visible items – Ali Apr 06 '21 at 04:57
  • 1
    I said you create a Global CountDown timer Which will provide you the second Tick. And inside both variant of bindViewHolder you can calculate the difference between `System.currentTimeInMillies-startTime` . Something like that . Now you will call `notifyItemChange(position,payload)` from timer `#Tick` method . – ADM Apr 06 '21 at 05:00
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/230786/discussion-between-ali-and-adm). – Ali Apr 06 '21 at 06:29

1 Answers1

1

Hi I have an idea using the Chronometer(https://developer.android.com/reference/android/widget/Chronometer). It is a child of TextView but handles the count and updating View on its own.

The idea is have the viewHolder of your list holds this Chronometer, then in your onBindViewHolder() just make this Chronometer count the correct time (if each item counting a different time).

Try this adapter, I have a separate timer for each item in the list, and onBindViewHolder I will bind that timer into the Chronometer. Try to see if you can replace myTimers[] using your timer class.


public class SOAdapter extends RecyclerView.Adapter<SOAdapter.ViewHolder> {

    Context context;
    int timerNumber;
    Chronometer[] myTimers;
    SOAdapter(Context context, int timerNumber){
        this.context = context;
        this.timerNumber = timerNumber;
        this.myTimers = new Chronometer[timerNumber];
        for (int i = 0; i < timerNumber; i++){
            Chronometer c = new Chronometer(context);
            c.setTextSize(30);
            c.setBase(SystemClock.elapsedRealtime());
            c.start();
            myTimers[i] = c;
        }
    }

    @NonNull
    @Override
    public SOAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        Chronometer timer = new Chronometer(context);
        timer.setLayoutParams(new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        timer.setTextSize(30);
        timer.setBase(SystemClock.elapsedRealtime());
        timer.start();
        return new ViewHolder(timer);
    }

    @Override
    public void onBindViewHolder(@NonNull SOAdapter.ViewHolder holder, int position) {
        Chronometer cur = myTimers[position];
        holder.bindTimer(cur);
    }

    @Override
    public int getItemCount() {
        return timerNumber;
    }


    public class ViewHolder extends RecyclerView.ViewHolder{
        Chronometer displayTimer;
        ViewHolder(Chronometer timer) {
            super(timer);
            displayTimer = timer;
        }

        public void bindTimer(Chronometer timer){
            displayTimer.setBase(timer.getBase());
        }
    }
}


BabyishTank
  • 1,329
  • 3
  • 18
  • 39
  • This sounds like a good option, let me try :) – Ali Apr 06 '21 at 05:01
  • Good idea BUT It seems like `ChronoMeter` only counts up and not down, I need to CountDown – Ali Apr 06 '21 at 07:40
  • If you are using API > 24, ChronoMeter support count down "The timer can also count downward towards the base time by setting setCountDown(boolean) to true. " This should solve the display problem, but I guess the hard part is need to update the base correctly during onBindViewHolder() – BabyishTank Apr 06 '21 at 14:28
  • 1
    yep thanks :) I will accept that as an answer as this was the best solution, I however ended up doing the CountDownTimer as global timer as ADM in above post suggested which also works fine. – Ali Apr 07 '21 at 00:23