-1

I am making some kind of Sale event app . For that i require a Recyclerview with each item having a countdown timer in it. Each items has countdown and it says "Sale begins in XX hr: YY min" And when the countdown of the item expires , the item should go down to the bottom of Recyclerview.

I have made a countdown timer inside onbindviewholder but i am getting error when the timer expires . Please someone help me overcome this... This is what is required img

The expiry time of each item is coming from Firestore database .

I am doing this in my onbindViewholder... String remTime;

@Override
public void onBindViewHolder(@NonNull Holder holder, int position) {
    ItemDet item = ItemArrayList.get(position);
    long timer = item.getCategoryTime().toDate().getTime();
    final long currentTime = System.currentTimeMillis();
    long expiryTime = timer - currentTime;

    if (holder.timer != null) {
        holder.timer.cancel();
    }

    holder.timer=new CountDownTimer(expiryTime, 1000) {
        public void onTick(long millisUntilFinished) {

            long seconds = millisUntilFinished / 1000;
            long minutes = seconds / 60;
            long hours = minutes / 60;
            long days = hours / 24;

            if(days==0){
                remTime= minutes % 60 + "m:" + seconds % 60+"s";
            }
            if(days<1 && hours>0 && minutes>0 && seconds>0){
                remTime= hours % 24 + "h:"+ minutes % 60 + "m";
            }
            if(days<1 && hours<1 && minutes>0 && seconds>0){
                remTime= minutes % 60 + "m:"+ seconds % 60+"s";
            }
            if(days<1 && hours<1 && minutes<1 && seconds>0){
                remTime= seconds % 60+"s";
            }
            if (days>0){
                remTime= days+"d, " +hours % 24 + "h";
            }
            holder.item_time_h.setText(remTime); 
        }

        public void onFinish() {
            holder.itemView.setEnabled(false);
            holder.item_time_h.setText("Time up!");

            ItemArrayList.remove(holder.adapterPosition());
            notifyItemRemoved(holder.adapterPosition());
            notifyItemRangeChanged(holder.adapterPosition(), ItemArrayList.size());
             
            //Here after timer finish i want to remove the item from the top and put it on bottom
            }

Please help me in this, i am stuck since 2 weeks

Avni Krtigya
  • 31
  • 1
  • 4
  • Have you tried using `notifyItemMoved(fromPosition, toPosition)` – Nitish Oct 21 '21 at 12:28
  • @Nitish ..Yes tried every thing...But when 1 item is removed either it gives me error ..or when the 2nd item is removed it gives me error – Avni Krtigya Oct 21 '21 at 12:32
  • What kind of error? – Nitish Oct 21 '21 at 12:33
  • Cannot call this method while RecyclerView is computing a layout or scrolling androidx.recyclerview.widget.RecyclerView{386d45e VFED..... ......ID 11,425-1069,1976 #7f0a0237 app:id/recyclerView2} – Avni Krtigya Oct 21 '21 at 12:35
  • @Nitish ava.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling androidx.recyclerview.widget.RecyclerView{826538d VFED..... ......ID 11,425-1069,1976 #7f0a0237 app:id/recyclerView2}, ada – Avni Krtigya Oct 21 '21 at 12:35
  • https://stackoverflow.com/questions/43221847/cannot-call-this-method-while-recyclerview-is-computing-a-layout-or-scrolling-wh - refer to this article on how to resolve this error – Nitish Oct 21 '21 at 12:38
  • @Nitish Thanks for your suggestions But I have seen it already & i am getting no idea how to use this... I have just started learning, this may be the reason i am not able to implement this – Avni Krtigya Oct 21 '21 at 12:42
  • @Nitish i tried this...still error java.lang.NullPointerException: Attempt to invoke virtual method 'boolean androidx.recyclerview.widget.RecyclerView.post(java.lang.Runnable)' on a null object reference – Avni Krtigya Oct 21 '21 at 13:05

1 Answers1

0

This shouldn't be hard. When the timer ends, you should move the item to the end of the list and then notify the adapter about this.

public void onFinish() {
    holder.itemView.setEnabled(false);
    holder.item_time_h.setText("Time up!");

    int oldPosition = holder.adapterPosition();
    ItemDet item = ItemArrayList.remove(oldPosition);
    ItemArrayList.add(item);
    int newPosition = ItemArrayList.size() - 1;
    notifyItemMoved(oldPosition, newPosition);
}

Hope I didn't make mistakes. I didn't test the code. Try this. I'm hoping for some feedback from you.

UPDATE

Now I see that your expiryTime depends on the System's time and it's not a fixed value. As the time passes, some of your Counters will finish counting. In that exact moment when the Counter finishes to count, if you would recompute expiryTime, its value will be 0. When you exit the fragment and reenter it, the fragment will get recreated, the recyclerview will get recreated and the expiryTime will get recomputed and it will be a negative value. And because of that the Counter's onFinish method will be called right away, when the recycler's layout is being created. A quick fix could be that to not start the timer if expiryTime is negative. For the moment I think you could do this:

final long currentTime = System.currentTimeMillis();
long expiryTime = timer - currentTime;

//ADD THIS HERE
if(expiryTime <= 0) {
    return;
}

if (holder.timer != null) {
    holder.timer.cancel();
}
daniyelp
  • 915
  • 1
  • 11
  • 26
  • Hey thanks for your answer..But i tried earlier also..It gives me this error....... Cannot call this method while RecyclerView is computing a layout or scrolling androidx.recyclerview.widget.RecyclerView{386d45e VFED..... ......ID 11,425-1069,1976 #7f0a0237 app:id/recyclerView2 – Avni Krtigya Oct 21 '21 at 14:20
  • Cannot call this method while RecyclerView is computing a layout or scrolling androidx.recyclerview.widget.RecyclerView{3370b04 VFED......... 11,425-1069,1976 #7f0a0237 app:id/recyclerView2}, adapter:com.example.pocketrocket.MyAdapter@68cc0b3, layout:androidx.recyclerview.widget.LinearLayoutManager@a16c670, context:com.example.pocketrocket.Scrolallgame@756e595 at androidx.recyclerview.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:3051) at androidx.recyclerview.widget.RecyclerView$RecyclerViewDataObserver.onItemRangeMoved(RecyclerView.java:5571) – Avni Krtigya Oct 21 '21 at 14:32
  • When exactly does the exception occur? Right in the beginning? Maybe the timer misbehaves and the method is triggered when the recycler layout is being loaded. – daniyelp Oct 21 '21 at 15:26
  • First item is removed fine, without any problem . 2nd item is also removed . App is not crashing if i dont go to any othet activity(If i remain on the same activity(better say fragment ) app is not crashing). App is crashing when i navigate and come to the fragment again. Also i would like to say that , item are just getting removed , not getting added back to bottom. It leaves a empty space after removing the item – Avni Krtigya Oct 21 '21 at 15:39
  • My recylerview is in a fragment...sorry i typed activity somewhere in the previous comment – Avni Krtigya Oct 21 '21 at 15:41
  • Using my code? And when you get back to the fragment that error is thrown right away? – daniyelp Oct 21 '21 at 15:51
  • When i get back... app is not even opening.. Error thrown straight away..yes – Avni Krtigya Oct 21 '21 at 15:56
  • Just give me 5 minutes..i am checking log that till which statement app is executing when i come again to the fragment... i would also like you to know that i am just using notifydatasetchanged() , when adding data by firebase database (i mean items). – Avni Krtigya Oct 21 '21 at 16:00
  • When i reopen the app or navigate execution is again coming to onfinish....Why is that item is already removed..Why its coming back to onfinish? – Avni Krtigya Oct 21 '21 at 16:11
  • The onfinish of the item that was removed before? – daniyelp Oct 21 '21 at 16:22
  • Yes... i dont know exactly ..but in log i am checking the item id and its coming back to onfinish , logging the category id and crashing..... – Avni Krtigya Oct 21 '21 at 16:28
  • Maybe the CountDownTimer has some weird behaviour.. Have you thought of replacing it with something else? – daniyelp Oct 21 '21 at 16:33
  • I’ll sugest some replacement when i get back home in an hour. – daniyelp Oct 21 '21 at 16:35
  • I saw somewhere that notifydataset and other things like that should not be done inside Onbindviewholder....check this https://stackoverflow.com/a/31069171/17072466 .. Can you figure something out of this? – Avni Krtigya Oct 21 '21 at 16:35
  • See my updated answer. I'm waiting for some feedback ;p – daniyelp Oct 21 '21 at 18:00
  • Yes it works .... its really a quick fix. But what's the catch? I mean you are saying its a fix. So whats a proper healthy cure for this ? By the way thanks so much for sharing this much ...you deserve the acceptance of answer. – Avni Krtigya Oct 21 '21 at 18:27
  • Thank. I'm glad it works. I'm saying it's a quick fix because, if you are unlucky, `expiryTime` can have the value `5` or idk `50` and this means that after only 50 milliseconds the `onFinish` will be called, but the recyclerview's layout can still be not fully finished by that time. – daniyelp Oct 21 '21 at 18:34
  • Also one thing that... the items after getting removed are not getting placed in the bottom.. rather its getting back the position in which it was...Example .. Item 1, 2, 3 , 4, 5 are their and suppose 1, 2 got removed after timer expired.... So new recylerview positing should be like 34512 ... but its same as like 12345 after also – Avni Krtigya Oct 21 '21 at 18:35
  • When you enter the fragment back? – daniyelp Oct 21 '21 at 18:37
  • You say you are getting your list from the Firebase. I think that when you get back to your fragment, the fragment will be recreated and hence also the list, because it will be fetched again from the Firebase. So the list that we updated in the adapter will be lost.. – daniyelp Oct 21 '21 at 19:00
  • This needs some thinking, yeah. It's not really a simple scenario. As for the text not being set properly, maybe try to put that `setText` before `setEnabled(false)`? I'm just making a guess here. I don't have crazy experience with xml.. – daniyelp Oct 21 '21 at 19:21
  • No no no daniyelp ..forget my previous comment...Your method is working absolutely fine . I was the one making mistake somewhere...After time expired the view at bottom was empty because by mistake i made visiblity GONE upon onfinish. Now i rectified it. YES I AM GETTING THE 3-4-5-1-2 pattern . but only problem that it vanishes once i navigate back to the fragment. – Avni Krtigya Oct 21 '21 at 19:23
  • What you could about this: In the fragment, before passing the list to the recycler's adapter, calculate the `expiryTime` and if it is negative or close to (zero + some margin) (100 or 200ms idk..), move it to the end of the list. After you make these changes, you can pass the modified list to the adapter. If you use this, then you can get rid of that "quick fix" I did in the `onBindViewHolder`. That's one solution I'm proposing. There may be others I think. – daniyelp Oct 21 '21 at 19:40
  • You mean which list..the item's expire time (1:00 pm , 1:05 pm , etc) or `(long expiryTime = timer - currentTime;)` I guess 2nd one.... So this needs to be calculated beforehand....and modified list should be passed.... well i am too dumb to understand this quickly. I need to think what you are saying. Can you show a just example how to pass modified list with the expirytime. yes i know i am dumb. Lol Can i contact you on telegram or twitter..i can show the code to you . – Avni Krtigya Oct 21 '21 at 20:01
  • First one. Instead of [1.00pm, 1.05pm, 2.00pm, 2.05pm, 2.30pm] you would send to the adapter [2.00pm, 2.05pm, 2.30pm, 1.00pm, 1.05pm], depending on the current Systime. You can email me at calciusivitaminad@gmail.com and we'll see from there what platform to use. But after 16 hours or so. Talking so much in the Stackoverflow's comments isn't a good thing. – daniyelp Oct 21 '21 at 20:25
  • ok understood.. sure will connect later – Avni Krtigya Oct 21 '21 at 20:37