Search code examples
javaandroidandroid-recyclerviewandroid-dateutils

How to update relative time in recyclerview?


I have a RecyclerView that displays items with a title, text and last edit time. enter image description here

In the RecyclerView adapter's onBindViewHolder I use getRelativeTimeSpanString and compare the note object's time to current time:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    Note note = getItem(position);
    holder.title.setText(note.getTitle());
    holder.text.setText(note.getText());

    String s = DateUtils.getRelativeTimeSpanString(note.getTime(), 
    System.currentTimeMillis(), 0L, DateUtils.FORMAT_ABBREV_ALL).toString();
    holder.timestamp.setText(s);
}

If I just added the item, it says "0 sec ago". How do I update that over time?

While writing this question, SO suggested this question: Updating the relative time in android , however I don't think updating the RecyclerView every X seconds is the the best idea:

  1. it drains battery
  2. it updates items that don't need updating (e.g., last update was 6 hours ago, but it's updating holder.timestamp every X seconds)

Is there a more efficient way to accomplish this?


Solution

  • Given you only need to update the UI when it is visible, you can reduce battery drain by only updating your UI when you know it's in the foreground using the onResume() and onPause() lifecycle callbacks in your Fragment/Acitvity. In addition, if minute granularity is good enough for your use case, you can use the ACTION_TIME_TICK system broadcast, which is fired every one minute, so you don't need to create your own timer needlessly.

    First, register a BroadcastReceiver to listen for this broadcast in onResume(), and unregister in onPause():

    @Override
    public void onResume() {
        final IntentFilter filter = new IntentFilter(Intent.ACTION_TIME_TICK);
        getContext().registerReceiver(receiver, filter);
    }
    
    @Override
    public void onPause() {
        getContext().unregisterReceiver(receiver);
    }
    

    Inside receiver, contain the logic for updating the relevant items of your RecyclerView (or delegate it to your Adapter, as a rough example:

    final BroadcastReceiver receiver = new BroadcastReceiver() {
       @Override
       public void onReceive(Context context, Intent intent) {
          adapter.updateTimes();
       }
    }
    
    //Inside your Adapter...
    void updateTimes() {
        for(int i = 0; i < notes.size(); i++) {
           final Note note = notes.get(i);
           //Pseudocode for determening whether or not an update it necessary
           //Actual implementation will depend on how you handle updates
           final String relative = getCurrentRelativeTimeString(); 
           if(!Objects.equals(relative, note.getRelativeTimeString())) {
               note.setRelativeTimeString(relative);
               notifyItemChanged(i);
           }
        }
    }